<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://docs.asmu1n.com/blog</id>
    <title>三木树屋 Blog</title>
    <updated>2026-07-27T12:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://docs.asmu1n.com/blog"/>
    <subtitle>三木树屋 Blog</subtitle>
    <icon>https://docs.asmu1n.com/img/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[Go Worker Pool 的两种思路实现]]></title>
        <id>https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown</id>
        <link href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown"/>
        <updated>2026-07-27T12:00:00.000Z</updated>
        <summary type="html"><![CDATA[写带结果收集的 worker pool，任务分发通常不难。真正容易写岔的是怎么停。]]></summary>
        <content type="html"><![CDATA[<p>写带结果收集的 worker pool，任务分发通常不难。真正容易写岔的是<strong>怎么停</strong>。</p>
<p>停之前先把这两个问题答死，后面实现几乎是顺水推舟：</p>
<ol>
<li class="">已经进队、还没被 worker 拿走的任务，还要不要做？</li>
<li class="">已经做完、结果还没发出去的任务，结果能不能丢？</li>
</ol>
<p>对这两个问题的回答不同，中断机制和 API 形态就会分叉。下面按这两种答案，分别看实现。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一先定语义再选手段">一、先定语义，再选手段<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E4%B8%80%E5%85%88%E5%AE%9A%E8%AF%AD%E4%B9%89%E5%86%8D%E9%80%89%E6%89%8B%E6%AE%B5" class="hash-link" aria-label="一、先定语义，再选手段的直接链接" title="一、先定语义，再选手段的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>维度</th><th>A：取消优先</th><th>B：排空优先</th></tr></thead><tbody><tr><td>中断信号</td><td>主要靠 <code>context</code> 取消</td><td>取消 + <code>close(taskQueue)</code></td></tr><tr><td>已入队未执行</td><td>放弃</td><td>继续取完并执行</td></tr><tr><td>已算出的结果</td><td>取消时可以丢</td><td>必须发出去</td></tr><tr><td>worker 退出</td><td><code>select</code> 听 <code>ctx.Done()</code></td><td><code>for range</code> 排空后退出</td></tr><tr><td>是否 close 任务队列</td><td>通常不关</td><td>要关</td></tr><tr><td>典型场景</td><td>超时、用户取消、快速失败</td><td>批处理、提交过的任务都要有交代</td></tr></tbody></table>
<p>没有谁绝对更好，只是「停止」的含义不同：</p>
<ul>
<li class=""><strong>A</strong>：别干了，能停就停。</li>
<li class=""><strong>B</strong>：不再接新活，但手里已接的活要做完（至少要给出结果）。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二两种中断机制各自��管什么">二、两种中断机制各自管什么<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E4%BA%8C%E4%B8%A4%E7%A7%8D%E4%B8%AD%E6%96%AD%E6%9C%BA%E5%88%B6%E5%90%84%E8%87%AA%E7%AE%A1%E4%BB%80%E4%B9%88" class="hash-link" aria-label="二、两种中断机制各自管什么的直接链接" title="二、两种中断机制各自管什么的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="context-取消">Context 取消<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#context-%E5%8F%96%E6%B6%88" class="hash-link" aria-label="Context 取消的直接链接" title="Context 取消的直接链接" translate="no">​</a></h3>
<p><code>cancel()</code> 是广播：所有听同一个 <code>ctx</code> 的地方都会收到。</p>
<p>它<strong>不会</strong>：</p>
<ul>
<li class="">清空 channel 里已有的数据</li>
<li class="">自动禁止继续往 channel 里发（发送方得自己先看 <code>ctx.Done()</code> 或别的停止标记）</li>
</ul>
<p>它只表示一件事：<strong>请尽快停下来</strong>。</p>
<p>所以 A 的 worker 常见写法是：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">select</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Done</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> task </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain">taskChan</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        res </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> task</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">select</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Done</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 结果可以丢</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> resultChan </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>Stop</code> 可以很短：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">cancel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">wg</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Wait</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">close</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">resultChan</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>队列里还没被取走的任务，就这么留在 channel 里，等 pool 不再被引用后一起被 GC 掉。</p>
<p>有两点别想当然：</p>
<ol>
<li class=""><strong><code>select</code> 并不保证「优先 Done」</strong>。<code>ctx.Done()</code> 和 <code>taskChan</code> 同时就绪时，Go 会随机选一个 case。取消之后，worker 仍可能再捞到一个任务；要更快停，任务函数本身也得认 <code>ctx</code>。</li>
<li class=""><strong>已经在跑的任务不会被 runtime 强杀</strong>。取消只是发信号；<code>task.Run(ctx)</code> 如果不看 <code>ctx</code>，照样跑完。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="channel-关闭">Channel 关闭<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#channel-%E5%85%B3%E9%97%AD" class="hash-link" aria-label="Channel 关闭的直接链接" title="Channel 关闭的直接链接" translate="no">​</a></h3>
<p><code>close(ch)</code> 的语义是：<strong>不会再有新数据了</strong>。</p>
<p><code>for range ch</code> 的保证是：先把缓冲里已有的值取完，再退出。</p>
<p>所以 B 的 worker 通常写成：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> t </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">range</span><span class="token plain"> taskQueue </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    val</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> t</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">fn</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 运行中仍可感知取消</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    resultQueue </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain"> Result</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">Val</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> val</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Err</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 结果必须送出，不再看 Done</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>Stop</code> 顺序要对：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">cancel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">           </span><span class="token comment" style="color:#999988;font-style:italic">// 让正在跑的任务有机会尽快收尾</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">close</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">taskQueue</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">// 让 range 把已入队任务排空</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">wg</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Wait</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">close</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">resultQueue</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 必须等 worker 都退出后再关</span><br></div></code></pre></div></div>
<p>这里「完整性」要说清楚：</p>
<ul>
<li class="">保证的是：<strong>已提交进队的任务都会被 worker 取到，并且对应产生一条结果</strong>。</li>
<li class=""><strong>不保证</strong>任务业务一定成功。<code>Stop</code> 时已经 <code>cancel</code> 了，若 <code>fn</code> 遵守 <code>ctx</code>，剩余任务可能很快返回 <code>ctx.Err()</code>。B 保证的是「有结果」，不是「一定算完正确答案」。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="一句话区分">一句话区分<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E4%B8%80%E5%8F%A5%E8%AF%9D%E5%8C%BA%E5%88%86" class="hash-link" aria-label="一句话区分的直接链接" title="一句话区分的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>ctx.Done()</code>：解决<strong>正在跑的工作</strong>怎么感知取消。</li>
<li class=""><code>close(taskQueue)</code>：解决<strong>队列里剩下的工作</strong>要不要做完。</li>
</ul>
<p>A 只用前者。B 把两者叠在一起用。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三停止流程">三、停止流程<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E4%B8%89%E5%81%9C%E6%AD%A2%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="三、停止流程的直接链接" title="三、停止流程的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="a取消优先">A：取消优先<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#a%E5%8F%96%E6%B6%88%E4%BC%98%E5%85%88" class="hash-link" aria-label="A：取消优先的直接链接" title="A：取消优先的直接链接" translate="no">​</a></h3>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">sequenceDiagram</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant Caller</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant Pool</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant Worker</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant TaskChan</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant ResultChan</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Caller-&gt;&gt;Pool: Stop()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Pool-&gt;&gt;Pool: cancel()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Pool-&gt;&gt;Worker: ctx.Done()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Note over Worker: 空闲或卡在结果发送时退出&lt;br/&gt;执行中的任务看 fn 是否认 ctx</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Note over TaskChan: 缓冲里未取走的任务被放弃</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Worker--&gt;&gt;Pool: wg.Done()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Pool-&gt;&gt;Pool: wg.Wait()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Pool-&gt;&gt;ResultChan: close()</span><br></div></code></pre></div></div>
<p>路径短，一般不用关任务队列，也少一层「往已关闭 channel 发送」的坑。代价是：未执行任务和部分结果都可能没了。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="b排空优先">B：排空优先<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#b%E6%8E%92%E7%A9%BA%E4%BC%98%E5%85%88" class="hash-link" aria-label="B：排空优先的直接链接" title="B：排空优先的直接链接" translate="no">​</a></h3>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">sequenceDiagram</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant Caller</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant Pool</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant Worker</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant TaskChan</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant ResultChan</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Caller-&gt;&gt;Pool: Stop()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Pool-&gt;&gt;Pool: cancel()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Note over Worker: 执行中的任务可感知取消</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Pool-&gt;&gt;TaskChan: close()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Note over Worker: range 继续取完已缓冲任务</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Worker-&gt;&gt;Worker: 执行剩余任务</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Worker-&gt;&gt;ResultChan: 阻塞发送 Result</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Worker--&gt;&gt;Pool: wg.Done()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Pool-&gt;&gt;Pool: wg.Wait()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Pool-&gt;&gt;ResultChan: close()</span><br></div></code></pre></div></div>
<p>已入队任务都会走到执行路径，结果不主动丢。代价是：提交与关闭有竞态，结果通道满了还可能把 <code>wg.Wait()</code> 卡死。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四结果发送和死锁">四、结果发送和死锁<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E5%9B%9B%E7%BB%93%E6%9E%9C%E5%8F%91%E9%80%81%E5%92%8C%E6%AD%BB%E9%94%81" class="hash-link" aria-label="四、结果发送和死锁的直接链接" title="四、结果发送和死锁的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="a可以丢结果所以不容易被结果通道卡住">A：可以丢结果，所以不容易被结果通道卡住<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#a%E5%8F%AF%E4%BB%A5%E4%B8%A2%E7%BB%93%E6%9E%9C%E6%89%80%E4%BB%A5%E4%B8%8D%E5%AE%B9%E6%98%93%E8%A2%AB%E7%BB%93%E6%9E%9C%E9%80%9A%E9%81%93%E5%8D%A1%E4%BD%8F" class="hash-link" aria-label="A：可以丢结果，所以不容易被结果通道卡住的直接链接" title="A：可以丢结果，所以�不容易被结果通道卡住的直接链接" translate="no">​</a></h3>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">select</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Done</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> resultChan </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>取消到来时，worker 可以选择不发结果直接退。这样即便没人读 <code>resultChan</code>，<code>wg.Wait()</code> 也不至于被满缓冲永久堵住。</p>
<p>收集侧可以 <code>Stop</code> 后再 <code>range</code>：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Stop</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> r </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">range</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Results</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 只包含停之前成功发出的结果</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="b结果不丢就必须先有人消费">B：结果不丢，就必须先有人消费<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#b%E7%BB%93%E6%9E%9C%E4%B8%8D%E4%B8%A2%E5%B0%B1%E5%BF%85%E9%A1%BB%E5%85%88%E6%9C%89%E4%BA%BA%E6%B6%88%E8%B4%B9" class="hash-link" aria-label="B：结果不丢，就必须先有人消费的直接链接" title="B：结果不丢，就必须先有人消费的直接链接" translate="no">​</a></h3>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resultQueue </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain"> Result</span><span class="token punctuation" style="color:#393A34">{</span><span class="token operator" style="color:#393A34">...</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 阻塞发送</span><br></div></code></pre></div></div>
<p>如果 <code>resultQueue</code> 缓冲满了，调用方还没开始读，worker 会堵在发送上，<code>wg.Wait()</code> 永远等不到，<code>Stop</code> 也回不来。</p>
<p>所以 B 的收集顺序不能反：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">done </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">make</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">chan</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">struct</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">go</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">defer</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">close</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">done</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> r </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">range</span><span class="token plain"> h</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Results</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        results </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">results</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> r</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">h</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Stop</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 内部 close(taskQueue) -&gt; wait workers -&gt; close(results)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain">done</span><br></div></code></pre></div></div>
<p>实务上更稳的做法是：提交阶段就一直有 consumer 在读；<code>Collect</code> 只是「停止接收新任务并收尾」。别把「首次消费」和「Stop」拧成一步再赌缓冲够不够大。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五关不关-taskqueue">五、关不关 taskQueue<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E4%BA%94%E5%85%B3%E4%B8%8D%E5%85%B3-taskqueue" class="hash-link" aria-label="五、关不关 taskQueue的直接链接" title="五、关不关 taskQueue的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="a可以不关">A：可以不关<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#a%E5%8F%AF%E4%BB%A5%E4%B8%8D%E5%85%B3" class="hash-link" aria-label="A：可以不关的直接链接" title="A：可以不关的直接链接" translate="no">​</a></h3>
<p>不关任务队列，从根上避开 <code>send on closed channel</code> 的 panic。</p>
<p>不关 <strong>不等于</strong> 泄漏。真正拖住内存和 goroutine 的，通常是忘了 <code>Stop</code>：worker 还活着，引用还在。正常 <code>Stop</code> 后 worker 因 <code>ctx.Done()</code> 退出，pool 不再被持有，channel 和残留任务可以被 GC 回收。和有没有 <code>close</code> 无关。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="b要关就得管提交竞态">B：要关，就得管提交竞态<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#b%E8%A6%81%E5%85%B3%E5%B0%B1%E5%BE%97%E7%AE%A1%E6%8F%90%E4%BA%A4%E7%AB%9E%E6%80%81" class="hash-link" aria-label="B：要关，就得管提交竞态的直接链接" title="B：要关，就得管提交竞态的直接链接" translate="no">​</a></h3>
<p><code>close(taskQueue)</code> 之后再 <code>Submit</code>，会 panic。常见处理：</p>
<ol>
<li class=""><strong>用 <code>mu + closed</code> 标志</strong>（更稳妥）：<code>Stop</code> 时先置位再关 channel；<code>Submit</code> 先看标志，已关闭就返回错误。</li>
<li class=""><strong><code>defer recover</code> 把 panic 转成错误</strong>：能兜住竞态窗口，但别把它当主设计。竞态不该靠 panic 当控制流。</li>
</ol>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">Pool</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">Submit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">task Task</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">mu</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Lock</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">defer</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">mu</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Unlock</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">closed </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> ErrPoolStopped</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">select</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Done</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Err</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">taskQueue </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain"> task</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">Pool</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">Stop</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">mu</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Lock</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">closed </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">closed </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">cancel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">close</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">taskQueue</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">mu</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Unlock</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">wg</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Wait</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">mu</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Lock</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">resultClosed </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">resultClosed </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">close</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">resultQueue</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">mu</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Unlock</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>Stop</code> 最好做成幂等。生产里谁调用几次 <code>Stop</code> 并不少见。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六怎么用">六、怎么用<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E5%85%AD%E6%80%8E%E4%B9%88%E7%94%A8" class="hash-link" aria-label="六、怎么用的直接链接" title="六、怎么用的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="批处理">批处理<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E6%89%B9%E5%A4%84%E7%90%86" class="hash-link" aria-label="批处理的直接链接" title="批处理的直接链接" translate="no">​</a></h3>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// A：停了就停，别假设「提交过的一定有结果」</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">p </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> Start</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> queueSize</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> workerSize</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> job </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">range</span><span class="token plain"> jobs </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token boolean" style="color:#36acaa">_</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Submit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">job</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">results </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ResultCollect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 取消后可能少于提交数</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// B：提交成功的任务，收尾时都该落到一条 Result</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">h </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> Start</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> workerCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> queueSize</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> i</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> job </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">range</span><span class="token plain"> jobs </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token boolean" style="color:#36acaa">_</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> h</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">SubmitWithIndex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">job</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> i</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">results </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> h</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Collect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 条数与成功提交数对齐；内容仍可能是 ctx.Err()</span><br></div></code></pre></div></div>
<p>如果业务要求「要么全做完，要么明确失败」，B 更合适，但要把 <code>Result.Err</code> 算进成功条件，不要只看切片长度。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="常驻服务">常驻服务<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E5%B8%B8%E9%A9%BB%E6%9C%8D%E5%8A%A1" class="hash-link" aria-label="常驻服务的直接链接" title="常驻服务的直接链接" translate="no">​</a></h3>
<p>两种实现都建议：<strong>独立 goroutine 持续读结果通道</strong>。结果缓冲一满，worker 发不出去，最终会反压到提交路径。</p>
<p><code>Stop</code> 之后的差别还是那句：</p>
<ul>
<li class="">A：尽快退，队列剩的可以不要。</li>
<li class="">B：队列剩的要跑完（或至少给出结果）再退。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七怎么选">七、怎么选<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E4%B8%83%E6%80%8E%E4%B9%88%E9%80%89" class="hash-link" aria-label="七、怎么选的直接链接" title="七、怎么选的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>你的真实需求</th><th>选</th></tr></thead><tbody><tr><td>超时 / 用户取消后希望尽快停</td><td>A</td></tr><tr><td>能接受丢掉未执行任务，也能接受丢部分结果</td><td>A</td></tr><tr><td>已经 <code>Submit</code> 成功的任务必须有一条结果</td><td>B</td></tr><tr><td>批处理要一次收齐「已提交任务」的去向</td><td>B</td></tr><tr><td>想实现简单，少处理 close/提交竞态</td><td>A</td></tr><tr><td>能接受多写一点关闭与反压逻辑</td><td>B</td></tr></tbody></table>
<p>选型时别把「API 好不好用」和「停止语义」绑死。带 index、可复用配置、预定义错误，A/B 都能做；那是产品封装问题，不是取消 vs 关闭的本质差异。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="八落地前的检查清单">八、落地前的检查清单<a href="https://docs.asmu1n.com/blog/2026/07/27/go-worker-pool-shutdown#%E5%85%AB%E8%90%BD%E5%9C%B0%E5%89%8D%E7%9A%84%E6%A3%80%E6%9F%A5%E6%B8%85%E5%8D%95" class="hash-link" aria-label="八、落地前的检查清单的直接链接" title="八、落地前的检查清单的直接链接" translate="no">​</a></h2>
<p>写 pool 之前，把下面几条勾清楚，基本就不会选错壳、也不会埋死锁：</p>
<ol>
<li class="">停止后，已入队任务是丢弃还是排空？</li>
<li class="">已算完的结果能不能丢？</li>
<li class="">任务函数认不认 <code>ctx</code>？不认的话，「快速取消」只是口号。</li>
<li class="">结果通道谁来读？读是从提交期就开始，还是 <code>Stop</code> 时才开始？</li>
<li class=""><code>Submit</code> 和 <code>Stop</code> 并发时，返回错误还是允许 panic？</li>
<li class=""><code>Stop</code> / <code>close(result)</code> 是否幂等？</li>
</ol>
<p>结论：</p>
<ul>
<li class="">若「停止」= <strong>立刻终止工作</strong>，Context 取消就够用。</li>
<li class="">若「停止」= <strong>不再接新任务，但把已接的收尾</strong>，就必须 <code>close</code> 任务队列，并认真处理结果投递、提交竞态和消费顺序。</li>
</ul>
<p>先定语义，再写代码。语义没定就先纠结要不要 <code>close</code>、要不要 <code>recover</code>，多半会把两种模型拧成一个四不像。</p>
<blockquote>
<p><em>本文部分内容由 AI 辅助生成</em></p>
</blockquote>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="Go" term="Go"/>
        <category label="后端" term="后端"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Go 的引用语义：指针、传参与那些天生的引用类型]]></title>
        <id>https://docs.asmu1n.com/blog/2026/07/14/go-reference-types</id>
        <link href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types"/>
        <updated>2026-07-14T12:00:00.000Z</updated>
        <summary type="html"><![CDATA[从 JavaScript / TypeScript 转过来学 Go 时，最容易困惑的一点是：Go 到底是"值传递"还是"引用传递"？答案是——Go 只有值传递，没有引用传递。但偏偏 slice、map、channel 这些类型用起来又像"引用"。这篇随记就来理清这层看似矛盾的关系：什么是引用语义、如何用指针显式取引用，以及为什么 channel、func、context 这些类型天生就不需要你再套一层指针。]]></summary>
        <content type="html"><![CDATA[<p>从 JavaScript / TypeScript 转过来学 Go 时，最容易困惑的一点是：Go 到底是"值传递"还是"引用传递"？答案是——<strong>Go 只有值传递，没有引用传递</strong>。但偏偏 <code>slice</code>、<code>map</code>、<code>channel</code> 这些类型用起来又像"引用"。这篇随记就来理清这层看似矛盾的关系：什么是引用语义、如何用指针显式取引用，以及为什么 <code>channel</code>、<code>func</code>、<code>context</code> 这些类型天生就不需要你再套一层指针。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一go-只有值传递">一、Go 只有值传递<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#%E4%B8%80go-%E5%8F%AA%E6%9C%89%E5%80%BC%E4%BC%A0%E9%80%92" class="hash-link" aria-label="一、Go 只有值传递的直接链接" title="一、Go 只有值传递的直接链接" translate="no">​</a></h2>
<p>先立一个地基：<strong>在 Go 中，函数传参、变量赋值，永远是把值复制一份。</strong> 没有任何例外。</p>
<p>那为什么改一个 slice 的元素，函数外面能看到变化？因为<strong>被复制的那个"值"本身可能就是一个包含指针的结构</strong>。复制这个结构时，内部的指针也被一起复制了，于是两个副本指向了同一块底层数据——这就是所谓的"引用语义"，它是值传递的一种自然结果，而不是什么特殊的"引用传递"。</p>
<p>理解了这一点，后面所有现象都能自洽解释。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二指针显式的引用">二、指针：显式的引用<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#%E4%BA%8C%E6%8C%87%E9%92%88%E6%98%BE%E5%BC%8F%E7%9A%84%E5%BC%95%E7%94%A8" class="hash-link" aria-label="二、指针：显式的引用的直接链接" title="二、指针：显式的引用的直接链接" translate="no">​</a></h2>
<p>当一个类型本身是"值语义"（比如 <code>int</code>、<code>struct</code>），而你又希望在别处修改它的本体时，就需要<strong>手动取指针</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="取地址与解引用">取地址与解引用<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#%E5%8F%96%E5%9C%B0%E5%9D%80%E4%B8%8E%E8%A7%A3%E5%BC%95%E7%94%A8" class="hash-link" aria-label="取地址与解引用的直接链接" title="取地址与解引用的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>&amp;</code> 取地址，得到一个指针。</li>
<li class=""><code>*</code> 解引用，通过指针访问或修改它指向的值。</li>
</ul>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">x </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">42</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">p </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">x          </span><span class="token comment" style="color:#999988;font-style:italic">// &amp;x 取 x 的地址，p 的类型是 *int</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">// 输出一个地址，如 0xc0000140a0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">*</span><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// *p 解引用，输出 42</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">*</span><span class="token plain">p </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token plain">         </span><span class="token comment" style="color:#999988;font-style:italic">// 通过指针修改本体</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">// 100，x 被改了</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="结构体指针会自动解引用">结构体指针会自动解引用<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#%E7%BB%93%E6%9E%84%E4%BD%93%E6%8C%87%E9%92%88%E4%BC%9A%E8%87%AA%E5%8A%A8%E8%A7%A3%E5%BC%95%E7%94%A8" class="hash-link" aria-label="结构体指针会自动解引用的直接链接" title="结构体指针会自动解引用的直接链接" translate="no">​</a></h3>
<p>Go 有一个贴心的语法糖：通过结构体指针访问字段时，<strong>不需要写 <code>(*p).Field</code>，直接 <code>p.Field</code> 即可</strong>，编译器帮你自动解引用。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> User </span><span class="token keyword" style="color:#00009f">struct</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Name </span><span class="token builtin">string</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">u </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> User</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">Name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"AsMuin"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">p </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">u</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Name </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Jason"</span><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// 等价于 (*p).Name = "Jason"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">// Jason</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="别忘了-nil-指针">别忘了 nil 指针<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#%E5%88%AB%E5%BF%98%E4%BA%86-nil-%E6%8C%87%E9%92%88" class="hash-link" aria-label="别忘了 nil 指针的直接链接" title="别忘了 nil 指针的直接链接" translate="no">​</a></h3>
<p>指针的零值是 <code>nil</code>。<strong>解引用一个 nil 指针会直接 panic</strong>，这是新手最常见的运行时崩溃来源之一。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> p </span><span class="token operator" style="color:#393A34">*</span><span class="token builtin">int</span><span class="token plain">             </span><span class="token comment" style="color:#999988;font-style:italic">// 零值为 nil</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// fmt.Println(*p)     // panic: runtime error: invalid memory address</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三天生具有引用语义的类型">三、天生具有引用语义的类型<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#%E4%B8%89%E5%A4%A9%E7%94%9F%E5%85%B7%E6%9C%89%E5%BC%95%E7%94%A8%E8%AF%AD%E4%B9%89%E7%9A%84%E7%B1%BB%E5%9E%8B" class="hash-link" aria-label="三、天生具有引用语义的类型的直接链接" title="三、天生具有引用语义的类型的直接链接" translate="no">​</a></h2>
<p>有一类类型，它们的底层实现本身就是（或包含）指针。复制它们时复制的是指针，因此<strong>天然共享底层数据，你无需再手动取地址</strong>。它们主要包括：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="slice-切片">slice 切片<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#slice-%E5%88%87%E7%89%87" class="hash-link" aria-label="slice 切片的直接链接" title="slice 切片的直接链接" translate="no">​</a></h3>
<p>切片是一个三元组结构：<code>{ 指向底层数组的指针, len, cap }</code>。复制切片会复制这个头部，但底层数组是共享的。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">modify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">s </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    s</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic">// 生效！修改的是共享的底层数组</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">nums </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">{</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">modify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">nums</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">nums</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">       </span><span class="token comment" style="color:#999988;font-style:italic">// [100 2 3]</span><br></div></code></pre></div></div>
<p><strong>但这里有个必须警惕的陷阱</strong>：修改元素能生效，<code>append</code> 却不一定。因为 <code>append</code> 在容量不足时会分配新的底层数组，而且 <code>len</code>/<code>cap</code> 只是头部里被复制的值，函数内的改动传不回去。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">grow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">s </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    s </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">s</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">999</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 可能换了底层数组，且 len 是副本 —— 外部看不到</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">nums </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">{</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">grow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">nums</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">nums</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">       </span><span class="token comment" style="color:#999988;font-style:italic">// 仍是 [1 2 3]，999 丢了</span><br></div></code></pre></div></div>
<p>所以涉及 <code>append</code> 的场景，要么<strong>返回新切片</strong>（<code>nums = grow(nums)</code>），要么传 <code>*[]int</code>。<strong>"切片是引用类型"这句话只对元素修改成立，对 <code>append</code> 不成立</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="map-映射">map 映射<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#map-%E6%98%A0%E5%B0%84" class="hash-link" aria-label="map 映射的直接链接" title="map 映射的直接链接" translate="no">​</a></h3>
<p><code>map</code> 在底层就是一个指向运行时哈希表结构（<code>runtime.hmap</code>）的指针。复制 map 变量就是复制那个指针，因此天然共享。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fill</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">m </span><span class="token keyword" style="color:#00009f">map</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    m</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"a"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic">// 直接生效，无需 *map</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">m </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">map</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">fill</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">m</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">m</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"a"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">     </span><span class="token comment" style="color:#999988;font-style:italic">// 1</span><br></div></code></pre></div></div>
<p><strong>你几乎永远不会写 <code>*map[string]int</code></strong>，那是多此一举。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="channel-通道">channel 通道<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#channel-%E9%80%9A%E9%81%93" class="hash-link" aria-label="channel 通道的直接链接" title="channel 通道的直接链接" translate="no">​</a></h3>
<p><code>channel</code> 同样是一个指向运行时结构（<code>runtime.hchan</code>）的指针。把 channel 传进函数、传给 goroutine，大家操作的都是同一个通道。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">worker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ch </span><span class="token keyword" style="color:#00009f">chan</span><span class="token plain"> </span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ch </span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">42</span><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 无需 *chan，直接就能通信</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ch </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">make</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">chan</span><span class="token plain"> </span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">worker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ch</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">&lt;-</span><span class="token plain">ch</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">       </span><span class="token comment" style="color:#999988;font-style:italic">// 42</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="func-函数值">func 函数值<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#func-%E5%87%BD%E6%95%B0%E5%80%BC" class="hash-link" aria-label="func 函数值的直接链接" title="func 函数值的直接链接" translate="no">​</a></h3>
<p>函数值本质上是指向函数（及其闭包环境）的引用。传递函数变量时共享的是同一个函数体，自然也不需要指针。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fn </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">fn</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"hello"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="interface-接口包括-context">interface 接口（包括 context）<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#interface-%E6%8E%A5%E5%8F%A3%E5%8C%85%E6%8B%AC-context" class="hash-link" aria-label="interface 接口（包括 context）的直接链接" title="interface 接口（包括 context）的直接链接" translate="no">​</a></h3>
<p>接口值是一个二元结构：<code>{ 类型信息, 指向数据的指针 }</code>。<strong><code>context.Context</code> 就是一个接口</strong>，这也是为什么它总是按值传递、且永远不需要 <code>*context.Context</code>。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">handler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx context</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Context</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 派生出带超时的子 context，返回的是新的接口值</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ctx</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cancel </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> context</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">WithTimeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> time</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Second</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">defer</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">cancel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">doSomething</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>context</code> 的设计哲学就是<strong>按值传递、层层派生</strong>：<code>WithCancel</code>、<code>WithTimeout</code>、<code>WithValue</code> 都返回一个包裹了父 context 的新值。给它取地址反而破坏了这套模型。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四函数传参到底用指针还是用值">四、函数传参：到底用指针还是用值<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#%E5%9B%9B%E5%87%BD%E6%95%B0%E4%BC%A0%E5%8F%82%E5%88%B0%E5%BA%95%E7%94%A8%E6%8C%87%E9%92%88%E8%BF%98%E6%98%AF%E7%94%A8%E5%80%BC" class="hash-link" aria-label="四、函数传参：到底用指针还是用值的直接链接" title="四、函数传参：到底用指针还是用值的直接链接" translate="no">​</a></h2>
<p>把上面的结论汇总成一条决策路径：</p>
<p><strong>1. 值语义类型</strong>（<code>int</code>、<code>float</code>、<code>bool</code>、<code>string</code>、<code>array</code>、<code>struct</code>）——默认按值拷贝。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">byValue</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u User</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Name </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"changed"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 改的是副本，外部无感</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">byPointer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">User</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Name </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"changed"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 改的是本体，外部可见</span><br></div></code></pre></div></div>
<p>需要用指针的两种情况：</p>
<ul>
<li class=""><strong>想在函数里修改调用方的本体</strong>。</li>
<li class=""><strong>结构体较大，想避免整体拷贝的开销</strong>（传指针只拷贝一个地址）。</li>
</ul>
<blockquote>
<p>特别注意 <code>string</code>：它虽然底层含指针，但<strong>不可变</strong>，表现为纯粹的值语义。想"修改"字符串只能重新赋值，改本体同样得靠 <code>*string</code>。数组 <code>[N]T</code> 则是<strong>完整深拷贝</strong>，大数组传参务必用切片或指针。</p>
</blockquote>
<p><strong>2. 天生引用语义类型</strong>（<code>slice</code>、<code>map</code>、<code>channel</code>、<code>func</code>、<code>interface</code>/<code>context</code>）——直接传，不要套指针。</p>
<p>给这些类型再包一层 <code>*</code> 不仅冗余，还会让代码变得别扭、不地道。唯一的例外仍是前面提到的 slice <code>append</code> 场景，那属于"需要改写头部本身"的特殊需求。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五总结">五、总结<a href="https://docs.asmu1n.com/blog/2026/07/14/go-reference-types#%E4%BA%94%E6%80%BB%E7%BB%93" class="hash-link" aria-label="五、总结的直接链接" title="五、总结的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>类型</th><th>底层构成</th><th>传参默认行为</th><th>修改本体是否需 <code>*</code></th></tr></thead><tbody><tr><td><code>int</code> / <code>bool</code> / <code>float</code></td><td>值</td><td>完整拷贝</td><td>需要</td></tr><tr><td><code>string</code></td><td>指针 + len（只读）</td><td>值语义拷贝</td><td>需要（且只能重新赋值）</td></tr><tr><td><code>array [N]T</code></td><td>值</td><td>完整深拷贝</td><td>需要</td></tr><tr><td><code>struct</code></td><td>值</td><td>完整拷贝</td><td>需要</td></tr><tr><td><code>pointer *T</code></td><td>地址</td><td>拷贝地址</td><td>本身即引用</td></tr><tr><td><code>slice</code></td><td>指针 + len + cap</td><td>拷贝头部，共享底层数组</td><td>元素改不用；<code>append</code> 需返回值或 <code>*[]T</code></td></tr><tr><td><code>map</code></td><td>指针</td><td>拷贝指针，共享</td><td>不需要</td></tr><tr><td><code>channel</code></td><td>指针</td><td>拷贝指针，共享</td><td>不需要</td></tr><tr><td><code>func</code></td><td>引用</td><td>拷贝引用</td><td>不需要</td></tr><tr><td><code>interface</code> / <code>context</code></td><td>类型 + 数据指针</td><td>拷贝二元结构</td><td>不需要</td></tr></tbody></table>
<p>一句话记忆：<strong>Go 永远是值传递；区别只在于——被拷贝的那个"值"里，有没有一个指向共享数据的指针。</strong> 有，就是引用语义，直接传；没有，想改本体就自己用 <code>&amp;</code> 取指针。</p>
<blockquote>
<p><em>本文部分内容由 AI 辅助生成</em></p>
</blockquote>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="Go" term="Go"/>
        <category label="后端" term="后端"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Chrome MV3 扩展架构： world 字段的原生 API 劫持与无 Background 通信实践]]></title>
        <id>https://docs.asmu1n.com/blog/2026/05/10/chrome-extension</id>
        <link href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension"/>
        <updated>2026-05-10T12:00:00.000Z</updated>
        <summary type="html"><![CDATA[MV3 下用 world: "MAIN" 劫持原生 API，并在无 Background 依赖时做跨 World 通信。]]></summary>
        <content type="html"><![CDATA[<p>MV3 下用 <code>world: "MAIN"</code> 劫持原生 API，并在无 Background 依赖时做跨 World 通信。</p>
<p>在 Web 调试与自动化测试场景中，扩展程序经常需要对目标页面的原生 API（如 <code>navigator.geolocation</code>、<code>navigator.userAgent</code>）进行劫持与 Mock。</p>
<p>在 Chrome Manifest V3 (MV3) 标准下，由于严格的执行上下文隔离（<code>World Isolation</code>）机制，实现高可靠的原生 API 劫持面临一定的架构挑战。本文将结合开源项目 <code>Anywhere Debugger</code> 的实践，探讨如何利用 MV3 的现代化特性，构建一套<strong>无 Background 依赖、纯原生、时序绝对可靠</strong>的 API 劫持与跨环境通信架构。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一-执行上下文隔离-world-isolation">一、 执行上下文隔离 (World Isolation)<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#%E4%B8%80-%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E9%9A%94%E7%A6%BB-world-isolation" class="hash-link" aria-label="一、 执行上下文隔离 (World Isolation)的直接链接" title="一、 执行上下文隔离 (World Isolation)的直接链接" translate="no">​</a></h2>
<p>Chrome 扩展的内容脚本（<code>Content Scripts</code>）默认运行在 <strong><code>ISOLATED World（隔离环境）</code></strong> 中。该环境具有以下特征：</p>
<ol>
<li class=""><strong>DOM 共享</strong>：可以读取和修改目标页面的 DOM 树。</li>
<li class=""><strong>API 权限</strong>：可以调用 <code>chrome.*</code> 扩展原生 API。</li>
<li class=""><strong>JS 堆栈隔离</strong>：无法访问目标页面 JS 上下文中的变量，也<strong>无法覆盖或劫持页面的原生 JS 对象</strong>。</li>
</ol>
<p>若在 ISOLATED 环境中执行 <code>navigator.userAgent = 'Mock UA'</code>，目标页面的业务脚本读取到的依然是真实的 UA。</p>
<p>传统方案通常在 ISOLATED 脚本中通过 <code>document.createElement('script')</code> 动态注入代码到页面。这种 Hack 手段存在两个致命缺陷：</p>
<ol>
<li class=""><strong>时序不可控</strong>：注入操作依赖 DOM 构建，极易晚于页面自身头部 <code>&lt;script&gt;</code> 的执行，导致劫持失败（即“空窗期”）。</li>
<li class=""><strong>CSP 限制</strong>：容易被目标页面的严格 Content-Security-Policy 拦截。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二-声明式双重注入">二、 声明式双重注入<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#%E4%BA%8C-%E5%A3%B0%E6%98%8E%E5%BC%8F%E5%8F%8C%E9%87%8D%E6%B3%A8%E5%85%A5" class="hash-link" aria-label="二、 声明式双重注入的直接链接" title="二、 声明式双重注入的直接链接" translate="no">​</a></h2>
<p>Chrome 111+ 在 MV3 的 <code>content_scripts</code> 配置中正式引入了 <code>world</code> 字段。该特性允许开发者以声明式的方式，将脚本直接注入到目标页面的 <strong>MAIN World（原生环境）</strong> 中。</p>
<p>基于此特性，我们设计了“声明式双重注入”架构，并<strong>完全移除了 Background Service Worker</strong>，以实现极简的单向控制流。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-manifest-配置解析">1. Manifest 配置解析<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#1-manifest-%E9%85%8D%E7%BD%AE%E8%A7%A3%E6%9E%90" class="hash-link" aria-label="1. Manifest 配置解析的直接链接" title="1. Manifest 配置解析的直接链接" translate="no">​</a></h3>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"manifest_version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Anywhere Debugger"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"1.0.0"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"action"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"default_popup"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"index.html"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"permissions"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"activeTab"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"storage"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"content_scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"matches"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"&lt;all_urls&gt;"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"js"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"mock_bridge.js"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"run_at"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"document_start"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"world"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ISOLATED"</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"matches"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"&lt;all_urls&gt;"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"js"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"mock_boot.js"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"run_at"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"document_start"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"world"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"MAIN"</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>架构职责划分：</strong></p>
<ul>
<li class=""><strong><code>mock_boot.js</code> (MAIN World)</strong>：引擎级注入，保证在页面任何脚本执行前运行（<code>document_start</code>）。负责执行 <code>Object.defineProperty</code> 劫持原生 API。无 <code>chrome.*</code> API 访问权限。</li>
<li class=""><strong><code>mock_bridge.js</code> (ISOLATED World)</strong>：作为通信网关。负责读取 <code>chrome.storage</code>，并桥接 Popup UI 与 MAIN World 之间的指令。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三-跨环境数据通信机制">三、 跨环境数据通信机制<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#%E4%B8%89-%E8%B7%A8%E7%8E%AF%E5%A2%83%E6%95%B0%E6%8D%AE%E9%80%9A%E4%BF%A1%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="三、 跨环境数据通信机制的直接链接" title="三、 跨环境数据通信机制的直接链接" translate="no">​</a></h2>
<p>由于 MAIN 与 ISOLATED 处于隔离的 JS 执行上下文（World）中，两者协同必须依赖跨上下文通信。严格说不一定是“进程间 IPC”，但扩展文档里常借 IPC 这个词指跨边界消息。本架构包含两条核心链路。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="链路-a页面加载初始化状态同步">链路 A：页面加载初始化（状态同步）<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#%E9%93%BE%E8%B7%AF-a%E9%A1%B5%E9%9D%A2%E5%8A%A0%E8%BD%BD%E5%88%9D%E5%A7%8B%E5%8C%96%E7%8A%B6%E6%80%81%E5%90%8C%E6%AD%A5" class="hash-link" aria-label="链路 A：页面加载初始化（状态同步）的直接链接" title="链路 A：页面加载初始化（状态同步）的直接链接" translate="no">​</a></h3>
<p>此链路在页面加载时自动触发，用于将扩展存储的 Mock 配置同步至原生环境。</p>
<ol>
<li class=""><strong>MAIN 侧就绪</strong>：<code>mock_boot.js</code> 同步执行，挂载 <code>window.addEventListener('message')</code> 监听器，并初始化劫持逻辑。</li>
<li class=""><strong>ISOLATED 侧读取</strong>：<code>mock_bridge.js</code> 异步调用 <code>chrome.storage.local.get</code> 获取持久化的 Mock 状态。</li>
<li class=""><strong>跨界推送</strong>：<code>mock_bridge.js</code> 通过 <code>window.postMessage</code> 将配置推送至 MAIN 环境，完成状态覆写。</li>
</ol>
<p><strong>技术细节：为何优先 <code>postMessage</code>，而不是 <code>CustomEvent</code>？</strong>
两个 World <strong>共享 DOM</strong>，在某些节点上用 <code>CustomEvent</code> 跨 World 通信并非绝对不可能，但 <code>window</code> 身份不同、代理行为与页面脚本劫持都会让 <code>CustomEvent</code> 非常容易踩坑。<code>window.postMessage</code> 走浏览器消息通道，语义清晰，是跨 World 通信更稳妥的默认选择（仍建议加来源校验、token 与命令白名单）。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="链路-b指令控制通道三级接力">链路 B：指令控制通道（三级接力）<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#%E9%93%BE%E8%B7%AF-b%E6%8C%87%E4%BB%A4%E6%8E%A7%E5%88%B6%E9%80%9A%E9%81%93%E4%B8%89%E7%BA%A7%E6%8E%A5%E5%8A%9B" class="hash-link" aria-label="链路 B：指令控制通道（三级接力）的直接链接" title="链路 B：指令控制通道（三级接力）的直接链接" translate="no">​</a></h3>
<p>当开发者在 Popup 面板中触发操作（如动态修改坐标）时，指令通过以下链路传递：</p>
<p><code>Popup (UI)</code> -&gt; <code>chrome.tabs.sendMessage</code> -&gt; <code>Bridge (ISOLATED)</code> -&gt; <code>window.postMessage</code> -&gt; <code>Boot (MAIN)</code></p>
<p>此链路无需 Background 参与，直接利用 <code>activeTab</code> 权限实现点对点精准投递。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四-核心代码实现">四、 核心代码实现<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#%E5%9B%9B-%E6%A0%B8%E5%BF%83%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0" class="hash-link" aria-label="四、 核心代码实现的直接链接" title="四、 核心代码实现的直接链接" translate="no">​</a></h2>
<p>以下为三级接力通信模型的核心代码实现，重点在于<strong>异步请求的跨界追踪</strong>与<strong>安全校验</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-popup-发起端-ui-context">1. Popup 发起端 (UI Context)<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#1-popup-%E5%8F%91%E8%B5%B7%E7%AB%AF-ui-context" class="hash-link" aria-label="1. Popup 发起端 (UI Context)的直接链接" title="1. Popup 发起端 (UI Context)的直接链接" translate="no">​</a></h3>
<p>封装基于 Promise 的命令发送工具，直接向当前活跃 Tab 发送指令。</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// src/utils/inject.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sendMockCommand</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">command</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> cmd</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> payload</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">tab</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> chrome</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">tabs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">query</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> active</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> currentWindow</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">tab</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"No active tab found"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 直接向 ISOLATED World 发送消息</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> chrome</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">tabs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">sendMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">tab</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> command</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-bridge-桥接端-isolated-world">2. Bridge 桥接端 (ISOLATED World)<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#2-bridge-%E6%A1%A5%E6%8E%A5%E7%AB%AF-isolated-world" class="hash-link" aria-label="2. Bridge 桥接端 (ISOLATED World)的直接链接" title="2. Bridge 桥接端 (ISOLATED World)的直接链接" translate="no">​</a></h3>
<p>负责接收扩展消息，并将其转换为 <code>postMessage</code>。为了解决 <code>postMessage</code> 的异步无回调问题，引入 <code>requestId</code> 进行请求追踪。</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// src/scripts/mock_bridge.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> pendingResponses </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Map</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name builtin">string</span><span class="token class-name punctuation" style="color:#393A34">,</span><span class="token class-name"> </span><span class="token class-name punctuation" style="color:#393A34">(</span><span class="token class-name">response</span><span class="token class-name operator" style="color:#393A34">:</span><span class="token class-name"> </span><span class="token class-name builtin">any</span><span class="token class-name punctuation" style="color:#393A34">)</span><span class="token class-name"> </span><span class="token class-name operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 1. 监听来自 Popup 的指令</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chrome</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">runtime</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">onMessage</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">addListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sender</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sendResponse</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> requestId </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> crypto</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">randomUUID</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 暂存原生 sendResponse 回调</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    pendingResponses</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">set</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">requestId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sendResponse</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 2. 跨界转发至 MAIN World</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    window</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'__MOCK_CMD__'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        requestId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">message</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> location</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">origin</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 返回 true 保持 MessageChannel 开启，等待异步响应</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 3. 监听来自 MAIN World 的执行结果</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">window</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">addEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'message'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">event</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">event</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">source </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> window </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> event</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">type </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'__MOCK_RESP__'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> requestId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> result </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> event</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> resolve </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pendingResponses</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">requestId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">resolve</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        pendingResponses</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">delete</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">requestId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-boot-执行端-main-world">3. Boot 执行端 (MAIN World)<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#3-boot-%E6%89%A7%E8%A1%8C%E7%AB%AF-main-world" class="hash-link" aria-label="3. Boot 执行端 (MAIN World)的直接链接" title="3. Boot 执行端 (MAIN World)的直接链接" translate="no">​</a></h3>
<p>负责接收指令并执行底层的 API 劫持。必须实施严格的来源校验，防止目标页面恶意脚本伪造指令。</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// src/scripts/mock_boot.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 预设的 Mock 状态</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> currentUA </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> navigator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userAgent</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 引擎级注入，立即劫持</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">defineProperty</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">navigator</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'userAgent'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">get</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> currentUA</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">window</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">addEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'message'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">event</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 基础过滤：只能降低噪声，页面脚本仍可伪造；需配合 token/nonce 和命令白名单使用</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">event</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">source </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> window </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> event</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">type </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'__MOCK_CMD__'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> cmd</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> payload</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> requestId </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> event</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cmd </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'setUA'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        currentUA </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> payload</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ua</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 执行完毕，沿原路返回响应</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        window</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'__MOCK_RESP__'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            requestId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            result</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> success</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> currentUA </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> location</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">origin</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五-总结">五、 总结<a href="https://docs.asmu1n.com/blog/2026/05/10/chrome-extension#%E4%BA%94-%E6%80%BB%E7%BB%93" class="hash-link" aria-label="五、 总结的直接链接" title="五、 总结的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>时序可靠性</strong>：利用 <code>world: "MAIN"</code> 与 <code>run_at: "document_start"</code>，从根本上消除了 DOM 注入带来的劫持空窗期，确保原生 API 在页面业务逻辑执行前已被接管。</li>
<li class=""><strong>最小权限原则</strong>：摒弃 Background Service Worker。对于仅需 UI 触发页面响应的工具类扩展，使用 <code>chrome.tabs.sendMessage</code> 进行点对点通信，可显著降低扩展的常驻内存开销。</li>
<li class=""><strong>职责边界清晰</strong>：MAIN 脚本专注于原生对象操作，ISOLATED 脚本专注于扩展 API 调用与消息路由，两者通过严谨的 IPC 协议解耦，提升了代码的可维护性与安全性。</li>
</ol>
<blockquote>
<p><em>本文部分内容由 AI 辅助生成</em></p>
</blockquote>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="TypeScript" term="TypeScript"/>
        <category label="基础设施" term="基础设施"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[RPC 和 Webhook 在架构中的设计考量]]></title>
        <id>https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook</id>
        <link href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook"/>
        <updated>2026-03-19T12:00:00.000Z</updated>
        <summary type="html"><![CDATA[RPC 像打电话，Webhook 像回执短信：两种跨系统协作方式的分工笔记。]]></summary>
        <content type="html"><![CDATA[<p>RPC 像打电话，Webhook 像回执短信：两种跨系统协作方式的分工笔记。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一-基础概念主动出击-vs-坐等通知">一、 基础概念：主动出击 vs 坐等通知<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#%E4%B8%80-%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5%E4%B8%BB%E5%8A%A8%E5%87%BA%E5%87%BB-vs-%E5%9D%90%E7%AD%89%E9%80%9A%E7%9F%A5" class="hash-link" aria-label="一、 基础概念：主动出击 vs 坐等通知的直接链接" title="一、 基础概念：主动出击 vs 坐等通知的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-rpc-remote-procedure-call微服务的内部专线">1. RPC (Remote Procedure Call)：微服务的“内部专线”<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#1-rpc-remote-procedure-call%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%9A%84%E5%86%85%E9%83%A8%E4%B8%93%E7%BA%BF" class="hash-link" aria-label="1. RPC (Remote Procedure Call)：微服务的“内部专线”的直接链接" title="1. RPC (Remote Procedure Call)：微服务的“内部专线”的直接链接" translate="no">​</a></h3>
<p>RPC 的全称是远程过程调用。它的核心设计哲学是：<strong>让分布式系统间的通信，看起来就像调用本地函数一样简单。</strong></p>
<p>在单体应用中，你调用 <code>getUser(id)</code> 是在内存里找数据；在微服务中，这个调用需要跨越网络。RPC 框架（如 gRPC、Dubbo）在底层帮你把网络连接（TCP 长连接）、数据压缩（Protobuf 二进制序列化）、多路复用等“脏活累活”全干了。</p>
<ul>
<li class=""><strong>形象比喻</strong>：RPC 就像是<strong>打电话</strong>。我主动拨号给你，你在电话那头立刻处理，我在这头拿着话筒（同步或伪同步）等你告诉我结果。</li>
<li class=""><strong>网络特征</strong>：通常运行在企业内网（Intranet），追求极致的低延迟和高并发，数据包体积被压缩到极致。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-webhook连接互联网孤岛的�信鸽">2. Webhook：连接互联网孤岛的“信鸽”<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#2-webhook%E8%BF%9E%E6%8E%A5%E4%BA%92%E8%81%94%E7%BD%91%E5%AD%A4%E5%B2%9B%E7%9A%84%E4%BF%A1%E9%B8%BD" class="hash-link" aria-label="2. Webhook：连接互联网孤岛的“信鸽”的直接链接" title="2. Webhook：连接互联网孤岛的“信鸽”的直接链接" translate="no">​</a></h3>
<p>Webhook 通常被称为“反向 API”或“HTTP 回调”。它的核心设计哲学是著名的好莱坞原则：<strong>“Don't call us, we'll call you.（别打电话给我们，有事我们会打给你）”</strong>。</p>
<p>它完全基于标准的 HTTP 协议。当某个事件在系统 A 中发生时，系统 A 会向系统 B 预先提供的一个 URL 发送一个 HTTP POST 请求（通常携带 JSON 数据）。</p>
<ul>
<li class=""><strong>形象比喻</strong>：Webhook 就像是<strong>留电话号码</strong>。我去办事大厅提交了材料，工作人员说：“你先回去吧，办好了我按你留的号码发短信通知你。”</li>
<li class=""><strong>网络特征</strong>：通常运行在公网（Internet），使用标准的 80/443 端口，极易穿透防火墙，天生适合跨越公司边界的通信。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二-两者在应用架构设计中承担的角色">二、 两者在应用架构设计中承担的角色<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#%E4%BA%8C-%E4%B8%A4%E8%80%85%E5%9C%A8%E5%BA%94%E7%94%A8%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E4%B8%AD%E6%89%BF%E6%8B%85%E7%9A%84%E8%A7%92%E8%89%B2" class="hash-link" aria-label="二、 两者在应用架构设计中承担的角色的直接链接" title="二、 两者在应用架构设计中承担的角色的直接链接" translate="no">​</a></h2>
<p>虽然通信方向（主动 vs 被动）和常见部署位置不同，但在很多业务场景下，RPC 和 Webhook 是<strong>同一需求的不同解法</strong>。共性在于：让两台计算机互相触发业务逻辑并传递数据。</p>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">sequenceDiagram</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant A as 系统 A</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant B as 系统 B</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Note over A,B: 同步 RPC 风格</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    A-&gt;&gt;B: 调用 getUser(id)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    B--&gt;&gt;A: 立即返回结果</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Note over A,B: Webhook 回调</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    A-&gt;&gt;B: 提交任务 + callback URL</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    B--&gt;&gt;A: 202 Accepted</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    B-&gt;&gt;A: 完成后 POST 结果到 Webhook</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="场景-1耗时任务的结果获取异步处理">场景 1：耗时任务的结果获取（异步处理）<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#%E5%9C%BA%E6%99%AF-1%E8%80%97%E6%97%B6%E4%BB%BB%E5%8A%A1%E7%9A%84%E7%BB%93%E6%9E%9C%E8%8E%B7%E5%8F%96%E5%BC%82%E6%AD%A5%E5%A4%84%E7%90%86" class="hash-link" aria-label="场景 1：耗时任务的结果获取（异步处理）的直接链接" title="场景 1：耗时任务的结果获取（异步处理）的直接链接" translate="no">​</a></h3>
<p>假设你的系统 A 需要调用系统 B 进行“4K 视频转码”或“AI 图像生成”，这个过程需要 5 分钟。</p>
<ul>
<li class=""><strong>若只用同步请求-响应式 RPC</strong>：系统 A 提交任务拿到 <code>TaskID</code> 后，容易退化成定时 <code>checkStatus(TaskID)</code> 轮询，产生大量无效请求。注意：<strong>RPC 本身也可以异步</strong>——消息队列、回调、gRPC streaming 等都能推送结果，并不等于“只能轮询”。</li>
<li class=""><strong>Webhook 的解法（HTTP 回调）</strong>：系统 A 提交任务时附带自己的 Webhook URL；完成后 B 主动 POST 结果。跨组织、公网场景特别常见。</li>
<li class=""><strong>结论</strong>：跨系统、跨信任边界的异步结果通知，Webhook 往往更省事；内网若已有事件总线/流式 RPC，不必强行上 Webhook。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="场景-2跨系统的状态同步与事件通知">场景 2：跨系统的状态同步与事件通知<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#%E5%9C%BA%E6%99%AF-2%E8%B7%A8%E7%B3%BB%E7%BB%9F%E7%9A%84%E7%8A%B6%E6%80%81%E5%90%8C%E6%AD%A5%E4%B8%8E%E4%BA%8B%E4%BB%B6%E9%80%9A%E7%9F%A5" class="hash-link" aria-label="场景 2：跨系统的状态同步与事件通知的直接链接" title="场景 2：跨系统的状态同步与事件通知的直接链接" translate="no">​</a></h3>
<p>假设用户在电商平台支付成功，需要通知订单系统“更改状态并准备发货”。</p>
<ul>
<li class=""><strong>内网环境用 RPC</strong>：如果支付系统和订单系统都是你们公司自己写的，部署在同一个机房。支付模块扣款成功后，直接发起内部 RPC 调用 <code>OrderService.updateStatus(Paid)</code>。内网极快，强一致性有保障。</li>
<li class=""><strong>外网环境用 Webhook</strong>：如果你对接的是<strong>微信支付/支付宝</strong>。微信不可能集成你公司的内部 RPC 框架。他们的做法是：让你在后台配置一个“支付成功回调地址（Webhook URL）”。用户付完钱，微信服务器会向你的公网 URL 发送带有签名验证的 HTTP 请求。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三rpc-在现代全栈框架中重塑新的开发范式">三、RPC 在现代全栈框架中重塑新的开发范式<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#%E4%B8%89rpc-%E5%9C%A8%E7%8E%B0%E4%BB%A3%E5%85%A8%E6%A0%88%E6%A1%86%E6%9E%B6%E4%B8%AD%E9%87%8D%E5%A1%91%E6%96%B0%E7%9A%84%E5%BC%80%E5%8F%91%E8%8C%83%E5%BC%8F" class="hash-link" aria-label="三、RPC 在现代全栈框架中重塑新的开发范式的直接链接" title="三、RPC 在现代全栈框架中重塑新的开发范式的直接链接" translate="no">​</a></h2>
<p>过去，我们总认为 RPC 是后端微服务（Java/Go/C++）的专利，前端和后端只能通过 RESTful API (HTTP/JSON) 或 GraphQL 交互。</p>
<p>但随着 <strong>Next.js、Nuxt.js</strong> 等全栈框架的崛起，以及 <strong>React Server Components (RSC)</strong> 和 <strong>Server Actions</strong> 的出现，<strong>RPC 风格的 DX（像调本地函数一样调服务端）</strong> 正在补充甚至替代一部分手写 REST 胶水代码。底层传输往往仍是 HTTP，不必理解成“RPC 全面取代 REST”。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-避免常规-http-请求带来的繁琐的-api-胶水代码">1. 避免常规 HTTP 请求带来的繁琐的 API 胶水代码<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#1-%E9%81%BF%E5%85%8D%E5%B8%B8%E8%A7%84-http-%E8%AF%B7%E6%B1%82%E5%B8%A6%E6%9D%A5%E7%9A%84%E7%B9%81%E7%90%90%E7%9A%84-api-%E8%83%B6%E6%B0%B4%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="1. 避免常规 HTTP 请求带来的繁琐的 API 胶水代码的直接链接" title="1. 避免常规 HTTP 请求带来的繁琐的 API 胶水代码的直接链接" translate="no">​</a></h3>
<p>在传统的 SPA（单页应用）开发中，前端要获取数据，必须经历痛苦的步骤：</p>
<ol>
<li class="">后端写一个 <code>/api/getUser</code> 的接口。</li>
<li class="">前端用 <code>fetch</code> 或 <code>axios</code> 发送 HTTP 请求。</li>
<li class="">前端处理 Loading 状态、解析 JSON、处理错误。</li>
</ol>
<p><strong>这本质上是在手动处理网络通信的细节。</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-nextjs-server-actions前端的隐形-rpc">2. Next.js Server Actions：前端的“隐形 RPC”<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#2-nextjs-server-actions%E5%89%8D%E7%AB%AF%E7%9A%84%E9%9A%90%E5%BD%A2-rpc" class="hash-link" aria-label="2. Next.js Server Actions：前端的“隐形 RPC”的直接链接" title="2. Next.js Server Actions：前端的“隐形 RPC”的直接链接" translate="no">​</a></h3>
<p>在 Next.js 14+ 中引入的 Server Actions，完美诠释了 RPC 的核心理念——<strong>“像调用本地函数一样调用远程代码”</strong>。</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// app/actions.js (运行在服务器端)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token string" style="color:#e3116c">'use server'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">updateUser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">id</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">update</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">where</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> id </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> data </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">success</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// app/page.jsx (运行在客户端浏览器)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> updateUser </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./actions'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:#d73a49">Profile</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">form action</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">formData</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// 看起来像是在调用一个普通的本地异步函数！</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">updateUser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">123</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> formData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'name'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">input name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"name"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">button type</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"submit"</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain">更新</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">button</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">form</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>前端代码直接 <code>import</code> 了一个只在服务器端运行的函数 <code>updateUser</code>，并在表单提交时直接调用了它。</p>
<p>Next.js 框架在编译时，会自动把这个函数调用转换成一个底层的 HTTP POST 请求。它帮你处理了序列化、网络传输、反序列化。开发者完全不需要写任何 <code>fetch</code> 代码，也不需要定义 API 路由。前端和后端共享了 TypeScript 的类型定义（端到端类型安全），体验极其丝滑。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-trpc进一步降低全栈开发的心智负担">3. tRPC：进一步降低全栈开发的心智负担<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#3-trpc%E8%BF%9B%E4%B8%80%E6%AD%A5%E9%99%8D%E4%BD%8E%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91%E7%9A%84%E5%BF%83%E6%99%BA%E8%B4%9F%E6%8B%85" class="hash-link" aria-label="3. tRPC：进一步降低全栈开发的心智负担的直接链接" title="3. tRPC：进一步降低全栈开发的心智负担的直接链接" translate="no">​</a></h3>
<p>除了框架自带的 Server Actions，社区中爆火的 <strong>tRPC</strong> 也是前端 RPC 化的典型代表。它允许你在不需要代码生成（像 gRPC 那样）的情况下，直接在前后端共享 TypeScript 类型，实现强类型的 RPC 调用，极大地提升了全栈开发效率。</p>
<p><strong>结论</strong>：RPC 的思想已经突破了后端内网的限制。在现代全栈开发中，<strong>RPC 正在取代一部分传统的 RESTful API，成为前后端无缝连接的新桥梁。</strong></p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四-架构选型到底该用谁">四、 架构选型：到底该用谁？<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#%E5%9B%9B-%E6%9E%B6%E6%9E%84%E9%80%89%E5%9E%8B%E5%88%B0%E5%BA%95%E8%AF%A5%E7%94%A8%E8%B0%81" class="hash-link" aria-label="四、 架构选型：到底该用谁？的直接链接" title="四、 架构选型：到底该用谁？的直接链接" translate="no">​</a></h2>
<p>在现代架构设计中，Webhook 和 RPC 绝不是竞争关系，而是<strong>完美的互补关系</strong>。我们可以通过一张表来清晰界定它们的适用边界：</p>
<table><thead><tr><th style="text-align:left">维度</th><th style="text-align:left">RPC (gRPC, Dubbo, Server Actions)</th><th style="text-align:left">Webhook</th></tr></thead><tbody><tr><td style="text-align:left"><strong>核心定位</strong></td><td style="text-align:left">系统内部/前后端直连的“隐形管道”</td><td style="text-align:left">跨越互联网孤岛的“信鸽”</td></tr><tr><td style="text-align:left"><strong>网络边界</strong></td><td style="text-align:left"><strong>内网主导</strong>（或同构框架内的前后端通信）</td><td style="text-align:left"><strong>公网主导</strong>，标准 HTTP 轻松穿透防火墙</td></tr><tr><td style="text-align:left"><strong>开发体验</strong></td><td style="text-align:left"><strong>极佳</strong>。像调用本地函数，强类型约束</td><td style="text-align:left"><strong>一般</strong>。需手动解析 JSON，处理重试逻辑</td></tr><tr><td style="text-align:left"><strong>耦合度</strong></td><td style="text-align:left"><strong>强耦合</strong>。需共享接口定义或类型系统</td><td style="text-align:left"><strong>极度松耦合</strong>。只要能解析 HTTP 即可对接</td></tr><tr><td style="text-align:left"><strong>适用场景</strong></td><td style="text-align:left">微服务内部通信、Next.js 全栈数据交互</td><td style="text-align:left">支付回调、GitHub 触发 CI/CD、SaaS 集成</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="总结建议">总结建议<a href="https://docs.asmu1n.com/blog/2026/03/19/rpc-webhook#%E6%80%BB%E7%BB%93%E5%BB%BA%E8%AE%AE" class="hash-link" aria-label="总结建议的直接链接" title="总结建议的直接链接" translate="no">​</a></h3>
<ol>
<li class=""><strong>如果你在做微服务拆分，或使用 Next.js 进行全栈开发</strong>：只要系统属于同一个组织，或者前后端代码在同一个代码库（Monorepo）中，毫不犹豫地拥抱 <strong>RPC</strong>（无论是 gRPC 还是 tRPC/Server Actions）。它能让你体验到极致的开发效率和性能。</li>
<li class=""><strong>如果你在做开放平台 (Open API)</strong>：需要与外部第三方平台（如 SaaS、支付网关、代码仓库）集成，或者需要提供事件订阅功能给你的客户， <strong>Webhook</strong> 是唯一的选择。</li>
</ol>
<p><strong>一句话概括</strong>：
RPC 让你在庞大的分布式系统（甚至跨越前后端）中，依然能体验到写单机代码的丝滑；而 Webhook 则赋予了你的系统与全世界任何其他互联网应用“握手联动”的能力。</p>
<blockquote>
<p><em>本文部分内容由 AI 辅助生成</em></p>
</blockquote>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="网络" term="网络"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[useSyncExternalStore]]></title>
        <id>https://docs.asmu1n.com/blog/2026/01/08/useSyncExternalStore</id>
        <link href="https://docs.asmu1n.com/blog/2026/01/08/useSyncExternalStore"/>
        <updated>2026-01-08T16:00:00.000Z</updated>
        <summary type="html"><![CDATA[在复杂模块里，把计算与状态放在 React 外的 store，再订阅到组件——useSyncExternalStore 的用法笔记。]]></summary>
        <content type="html"><![CDATA[<p>在复杂模块里，把计算与状态放在 React 外的 store，再订阅到组件——<code>useSyncExternalStore</code> 的用法笔记。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://docs.asmu1n.com/blog/2026/01/08/useSyncExternalStore#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>在逻辑较复杂的模块中，我通常不会把重计算全部内联进 Hook，而是封装成外部 store（常见单例），再把状态接到 React 视图。</p>
<p>常见接法：</p>
<ol>
<li class=""><code>useState</code> + <code>useEffect</code> 手动订阅</li>
<li class=""><code>useRef</code> 读最新值（<strong>不会</strong>触发渲染）</li>
<li class=""><strong><code>useSyncExternalStore</code></strong> 按外部快照订阅（本文重点）</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="概念">概念<a href="https://docs.asmu1n.com/blog/2026/01/08/useSyncExternalStore#%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="概念的直接链接" title="概念的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>概念</th><th>含义</th></tr></thead><tbody><tr><td><code>store</code></td><td>脱离 React 的数据源</td></tr><tr><td><code>subscribe</code></td><td>注册监听，状态变时通知</td></tr><tr><td><code>getSnapshot</code></td><td>返回当前快照；未变时引用要稳定</td></tr><tr><td><code>updateAction</code></td><td>改状态并 <code>notify</code></td></tr><tr><td>不可变数据</td><td>更新时换新对象，而不是改旧对象字段</td></tr></tbody></table>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">sequenceDiagram</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant UI as React 组件</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant Hook as useSyncExternalStore</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant Store as 外部 Store</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    UI-&gt;&gt;Hook: 订阅</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Hook-&gt;&gt;Store: subscribe(listener)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Hook-&gt;&gt;Store: getSnapshot()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Store--&gt;&gt;Hook: 同一引用(未变) / 新引用(已变)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Note over Store: updateAction</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Store-&gt;&gt;Store: 换新 snapshot + notify</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Store--&gt;&gt;Hook: listener()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Hook-&gt;&gt;UI: 重新渲染</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实践">实践<a href="https://docs.asmu1n.com/blog/2026/01/08/useSyncExternalStore#%E5%AE%9E%E8%B7%B5" class="hash-link" aria-label="实践的直接链接" title="实践的直接链接" translate="no">​</a></h2>
<p>快照里尽量放<strong>纯数据</strong>。若 <code>friends</code> 存 <code>Student</code> 类实例，实例内部仍可变，会破坏「快照不可变」的叙事；下面用 <code>StudentState</code> / id 列表。</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> useSyncExternalStore </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">StudentState</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    age</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    friendIds</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">StudentStore</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> snapshot</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> StudentState</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> listeners</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> age</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">subscribe </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">subscribe</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">bind</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">getSnapshot </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getSnapshot</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">bind</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">getServerSnapshot </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getServerSnapshot</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">bind</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            name</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            age</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            friendIds</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setName</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">name </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> name </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">_notify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setAge</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">age</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">age </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> age</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> age </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">_notify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">addFriendId</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">friendIds</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token operator" style="color:#393A34">...</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            friendIds</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token operator" style="color:#393A34">...</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">friendIds</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">_notify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">removeFriendId</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token operator" style="color:#393A34">...</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            friendIds</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">friendIds</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">f </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> f </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">_notify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">subscribe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function-variable function" style="color:#d73a49">listener</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">listeners</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">push</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">listener</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">listeners </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">listeners</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">l </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> l </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> listener</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/** 客户端：未变化时必须返回同一引用（Object.is） */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getSnapshot</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">     * SSR / 水合：服务端渲染时用的快照。</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">     * 若不需要 SSR，可与 getSnapshot 相同；有差异时要避免 hydration mismatch。</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">     */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getServerSnapshot</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">_notify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">listeners</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">forEach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">listener </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">listener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> studentStore </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">StudentStore</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'John'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">20</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useStudent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> studentState </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useSyncExternalStore</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        studentStore</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">subscribe</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        studentStore</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">getSnapshot</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        studentStore</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">getServerSnapshot</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        student</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> studentState</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        actions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> studentStore</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="职责对照">职责对照<a href="https://docs.asmu1n.com/blog/2026/01/08/useSyncExternalStore#%E8%81%8C%E8%B4%A3%E5%AF%B9%E7%85%A7" class="hash-link" aria-label="职责对照的直接链接" title="职责对照的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>updateAction</strong>：<code>setName</code> / <code>setAge</code> / <code>addFriendId</code> 等，改完必须换新 snapshot 再 <code>_notify</code></li>
<li class=""><strong>subscribe</strong>：登记 listener，返回取消订阅函数</li>
<li class=""><strong>getSnapshot</strong>：返回当前快照引用</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心注意事项">核心注意事项<a href="https://docs.asmu1n.com/blog/2026/01/08/useSyncExternalStore#%E6%A0%B8%E5%BF%83%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" class="hash-link" aria-label="核心注意事项的直接链接" title="核心注意事项的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>未变化时引用稳定</strong>：多次 <code>getSnapshot()</code> 要用 <code>Object.is</code> 相等。</li>
</ol>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 反例：每次 new 对象 → 永远认为变了 → 无限重渲染风险</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getSnapshot</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">snapshot </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<ol start="2">
<li class=""><strong>快照尽量是可序列化的纯数据</strong>，不要塞可变类实例当「真理源」。</li>
<li class=""><strong>SSR</strong> 记得第三参数 <code>getServerSnapshot</code>，否则在服务端/水合路径可能告警或 mismatch。</li>
<li class="">与 <code>useEffect</code> 手写订阅相比，<code>useSyncExternalStore</code> 更利于避免并发渲染下的 <strong>tearing</strong>（读到不一致的外部快照）。</li>
</ol>
<p>一句话：<strong>外部 store 负责可变状态机；交给 React 的永远是「引用稳定的不可变快照」。</strong></p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="React" term="React"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[自定义主题色]]></title>
        <id>https://docs.asmu1n.com/blog/2025/09/19/customTheme</id>
        <link href="https://docs.asmu1n.com/blog/2025/09/19/customTheme"/>
        <updated>2025-09-19T16:00:00.000Z</updated>
        <summary type="html"><![CDATA[运行时切换主题色：CSS 变量 + 关键色派生。]]></summary>
        <content type="html"><![CDATA[<p>运行时切换主题色：CSS 变量 + 关键色派生。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://docs.asmu1n.com/blog/2025/09/19/customTheme#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>多套配色（节日皮肤、品牌换皮、用户自定义）往往不能只靠「浅色/深色」两套写死的编译结果。更灵活的做法是：</p>
<ol>
<li class="">样式里用 <strong>CSS 变量</strong>，不写死具体色值</li>
<li class="">运行时用 JS 改 <code>document.documentElement</code> 上的变量</li>
<li class="">若大量颜色由主色推导，可用派生算法减少手写 token</li>
</ol>
<p>编译期工具（Sass / Less / PostCSS）仍然有用：它们适合写出「引用 <code>var(--x)</code> 的结构」。无法单独满足的是：<strong>用户随时改主色时，编译期写死的派生结果跟不上</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实践">实践<a href="https://docs.asmu1n.com/blog/2025/09/19/customTheme#%E5%AE%9E%E8%B7%B5" class="hash-link" aria-label="实践的直接链接" title="实践的直接链接" translate="no">​</a></h2>
<p>假设有三个关键色：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--primary-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token color function" style="color:#d73a49">rgb</span><span class="token color punctuation" style="color:#393A34">(</span><span class="token color number" style="color:#36acaa">179</span><span class="token color punctuation" style="color:#393A34">,</span><span class="token color"> </span><span class="token color number" style="color:#36acaa">30</span><span class="token color punctuation" style="color:#393A34">,</span><span class="token color"> </span><span class="token color number" style="color:#36acaa">30</span><span class="token color punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--secondary-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#00ff00</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--accent-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#0000ff</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>主色若有多个深浅档，可以手写：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--primary-color-light</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#ff0000</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--primary-color-dark</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token color function" style="color:#d73a49">rgb</span><span class="token color punctuation" style="color:#393A34">(</span><span class="token color number" style="color:#36acaa">112</span><span class="token color punctuation" style="color:#393A34">,</span><span class="token color"> </span><span class="token color number" style="color:#36acaa">4</span><span class="token color punctuation" style="color:#393A34">,</span><span class="token color"> </span><span class="token color number" style="color:#36acaa">4</span><span class="token color punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>档位一多就难维护。可用 <code>color-mix()</code> 派生：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--primary-color-light</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">color-mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">in srgb</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary-color</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">90</span><span class="token unit">%</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token hexcode color">#fff</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--primary-color-dark</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">color-mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">in srgb</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary-color</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">90</span><span class="token unit">%</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token hexcode color">#000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>兼容性：</strong> <code>color-mix</code> 支持率在提升，但仍要以目标用户浏览器为准（查 <a href="https://caniuse.com/mdn-css_types_color_color-mix" target="_blank" rel="noopener noreferrer" class="">Can I Use: color-mix()</a>）。不满足时，用 JS 派生后写入变量更稳。</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> colord</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> extend </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'colord'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">mixPlugin</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'colord/plugins/mix'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">extend</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">mixPlugin</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateThemeColors</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">baseColor</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> base </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">colord</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">baseColor</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-50'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'#FFFFFF'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.95</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-100'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'#FFFFFF'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.9</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-200'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'#FFFFFF'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.75</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-300'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'#FFFFFF'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.5</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-400'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'#FFFFFF'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.25</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-500'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-600'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'#000000'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-700'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'#000000'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.25</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-800'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'#000000'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.5</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-900'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'#000000'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.75</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-property property" style="color:#36acaa">'primary-950'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> base</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'#000000'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.9</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/** 把派生结果注入 :root，样式侧继续用 var(--primary-500) 等 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">applyThemeColors</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">baseColor</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> colors </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateThemeColors</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">baseColor</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> root </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token dom variable" style="color:#36acaa">document</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">documentElement</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token known-class-name class-name">Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">entries</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">colors</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">forEach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter punctuation" style="color:#393A34">[</span><span class="token parameter">key</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> value</span><span class="token parameter punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        root</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">style</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">setProperty</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">--</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">key</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 用户选色 / 读 localStorage 后</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// applyThemeColors('#b31e1e');</span><br></div></code></pre></div></div>
<p>业务 CSS 只认变量：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector class" style="color:#00009f">.button-primary</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary-500</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">border-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary-600</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.button-primary</span><span class="token selector pseudo-class" style="color:#00009f">:hover</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary-400</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">flowchart LR</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    U[用户选择主色] --&gt; JS[generateThemeColors]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    JS --&gt; Root[写入 CSS 变量到 :root]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Root --&gt; CSS[组件样式 var --primary-*]</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://docs.asmu1n.com/blog/2025/09/19/customTheme#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>方案</th><th>优点</th><th>局限</th></tr></thead><tbody><tr><td>编译期多套主题文件</td><td>简单、无运行时成本</td><td>不适合任意用户色</td></tr><tr><td><code>color-mix</code> + CSS 变量</td><td>声明式派生</td><td>兼容性、调试手感</td></tr><tr><td>JS 派生 + <code>setProperty</code></td><td>灵活、可持久化</td><td>首屏要避免闪烁（可内联关键脚本）</td></tr></tbody></table>
<p><strong>推荐组合：</strong> 样式结构用变量写好（构建工具照常参与）；运行时只改变量值，而不是整表重编译 CSS。</p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="CSS" term="CSS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[在 React + Nest.js 项目里做混合 SSR（不是 RSC）]]></title>
        <id>https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid</id>
        <link href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid"/>
        <updated>2025-07-03T16:00:00.000Z</updated>
        <summary type="html"><![CDATA[为一个 React + Nest.js 的前后端分离项目，针对特定路由增加服务端渲染（SSR + Hydration）。注意：本文讨论的是传统 SSR 水合，不是 React Server Components（RSC）。]]></summary>
        <content type="html"><![CDATA[<p>为一个 <code>React</code> + <code>Nest.js</code> 的前后端分离项目，针对特定路由增加服务端渲染（SSR + Hydration）。注意：本文讨论的是<strong>传统 SSR 水合</strong>，不是 React Server Components（RSC）。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="先分清ssr--rsc">先分清：SSR ≠ RSC<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#%E5%85%88%E5%88%86%E6%B8%85ssr--rsc" class="hash-link" aria-label="先分清：SSR ≠ RSC的直接链接" title="先分清：SSR ≠ RSC的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>概念</th><th>是什么</th></tr></thead><tbody><tr><td><strong>SSR</strong></td><td>服务端把组件渲染成 HTML 字符串发回浏览器，客户端再 <strong>hydrate</strong> 挂上事件与状态</td></tr><tr><td><strong>RSC</strong></td><td>React 的服务端组件模型：服务端组件代码默认不进客户端包，通过 Server/Client 边界与 Flight 协议协作</td></tr></tbody></table>
<p>下文全程是 <strong>SSR + Hydration</strong> 的混合渲染改造，不要和 RSC 混为一谈。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="从纯-csr-到混合渲染">从纯 <code>CSR</code> 到混合渲染<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#%E4%BB%8E%E7%BA%AF-csr-%E5%88%B0%E6%B7%B7%E5%90%88%E6%B8%B2%E6%9F%93" class="hash-link" aria-label="从纯-csr-到混合渲染的直接链接" title="从纯-csr-到混合渲染的直接链接" translate="no">​</a></h3>
<p>在现代 Web 开发中，前后端分离已经成为主流，便于在浏览器上实现复杂交互。</p>
<p>但并非所有内容都需要在客户端用 JS 驱动。许多数据展示页只是“载入数据并展示”，纯 CSR 会让首屏几乎空白、依赖后续 JS 渲染，在加载性能和 SEO 上有明显短板。</p>
<p>当我们的项目逐渐成熟，希望优化关键页面（如用户个人资料页、产品详情页）的加载性能和 <code>SEO</code> 时，是否必须推倒重来，迁移到 <code>Next.js</code> 或 <code>Remix</code> 这类一体化框架？</p>
<p>答案是：不必。我们完全可以在现有的 <code>CSR</code> + <code>API</code> 架构上，通过“混合渲染”的模式，针对性地为特定路由增加服务端渲染 (<code>SSR</code>) 功能。本文以一个 <code>React</code> + <code>Nest.js</code>项目，为 /user 路由添加服务端渲染为例。(伪代码，仅供思路参考)</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心思想从分离到协作">核心思想：从“分离”到“协作”<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#%E6%A0%B8%E5%BF%83%E6%80%9D%E6%83%B3%E4%BB%8E%E5%88%86%E7%A6%BB%E5%88%B0%E5%8D%8F%E4%BD%9C" class="hash-link" aria-label="核心思想：从“分离”到“协作”的直接链接" title="核心思想：从“分离”到“协作”的直接链接" translate="no">​</a></h3>
<p>要实现混合渲染，我们需要调整前后端的角色定位：</p>
<p><code>Nest.js</code> 的角色扩展：对于大部分 <code>API</code> 请求，它依然是纯粹的 <code>API</code> 服务器。但对于 /user 这个特定路由的 <code>GET</code> 请求，它将化身为一个 <code>Web</code> 服务器，负责：</p>
<p>调用内部服务获取数据。</p>
<p>在服务端将 <code>React</code> 组件和数据“渲染”成一份完整的 <code>HTML</code> 字符串。</p>
<p>将这份 <code>HTML</code> 返回给浏览器。</p>
<p><code>React</code> 的“水合”(<code>Hydration</code>)：浏览器先展示服务端 HTML，客户端 React 加载后<strong>接管已有 DOM</strong>，挂上事件与状态，让页面变成可交互的 SPA，而不是推倒重建。</p>
<p>其他路由保持不变：除了 <code>/user</code>，如 <code>/dashboard</code>、<code>/settings</code> 仍可走纯 CSR，由 <code>React Router</code> 控制，后端只提供 API。</p>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">sequenceDiagram</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant B as 浏览器</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant N as Nest.js</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant R as React 服务端渲染</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant API as 内部服务/DB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    B-&gt;&gt;N: GET /user</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    N-&gt;&gt;API: 取页面数据</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    API--&gt;&gt;N: user</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    N-&gt;&gt;R: renderToString(UserPage, props)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    R--&gt;&gt;N: HTML 片段</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    N--&gt;&gt;B: 完整 HTML + 初始数据脚本</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    B-&gt;&gt;B: 展示内容</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    B-&gt;&gt;B: 加载 JS 后 hydrateRoot 接管</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="后端改造让-nestjs-具备渲染能力">后端改造：让 <code>Nest.js</code> 具备渲染能力<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#%E5%90%8E%E7%AB%AF%E6%94%B9%E9%80%A0%E8%AE%A9-nestjs-%E5%85%B7%E5%A4%87%E6%B8%B2%E6%9F%93%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="后端�改造让-nestjs-具备渲染能力的直接链接" title="后端改造让-nestjs-具备渲染能力的直接链接" translate="no">​</a></h3>
<p>这是本次改造的重点。我们需要赋予 <code>Nest.js</code> “说 <code>React</code> 的语言”的能力。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="1安装依赖">1.安装依赖<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#1%E5%AE%89%E8%A3%85%E4%BE%9D%E8%B5%96" class="hash-link" aria-label="1.安装依赖的直接链接" title="1.安装依赖的直接链接" translate="no">​</a></h4>
<p>首先，在 <code>Nest.js</code> 项目中安装 <code>React</code> 相关的库。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="2在你的-nestjs-项目目录下">2.在你的 NestJS 项目目录下<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#2%E5%9C%A8%E4%BD%A0%E7%9A%84-nestjs-%E9%A1%B9%E7%9B%AE%E7%9B%AE%E5%BD%95%E4%B8%8B" class="hash-link" aria-label="2.在你的 NestJS 项目目录下的直接链接" title="2.在你的 NestJS 项目目录下的直接链接" translate="no">​</a></h4>
<p>npm install react react-dom</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="3创建-ssr-controller">3.创建 SSR Controller<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#3%E5%88%9B%E5%BB%BA-ssr-controller" class="hash-link" aria-label="3.创建 SSR Controller的直接链接" title="3.创建 SSR Controller的直接链接" translate="no">​</a></h4>
<p>我们新建一个 <code>Controller</code>，专门用于处理 <code>SSR</code> 请求。</p>
<div class="language-TypeScript language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// nestjs-project/src/ssr/ssr.controller.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> Controller</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Get</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Res </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@nestjs/common'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> Response </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'express'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> fs </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'fs'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> path </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'path'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 导入 React 和 ReactDOMServer</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> React </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> ReactDOMServer </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react-dom/server'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 注意：SSR 需要可在 Node 中执行的 server bundle（不要直接 require 仅面向浏览器的 chunk）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 下面路径仅为示意</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> UserPage </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'../../react-build/server/UserPage.js'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">default</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 假设你有一个 UserService 来获取数据</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> UserService </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'../user/user.service'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">Controller</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">SsrController</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">readonly</span><span class="token plain"> userService</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> UserService</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">Get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'user'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">renderUserPage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">Res</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> res</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Response</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 步骤 1: 在服务端获取页面所需数据</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userService</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findOne</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'some-user-id'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 步骤 2: 将 React 组件和数据渲染成 HTML 字符串</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> appHtml </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ReactDOMServer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">renderToString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">createElement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">UserPage</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> initialUser</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 步骤 3: 读取客户端的 HTML 模板</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 这个模板是 React 项目 build 后的 index.html</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> templatePath </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./path/to/react-project/build'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'index.html'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> htmlTemplate </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> fs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">readFileSync</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">templatePath</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'utf8'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 步骤 4: 将渲染的 HTML 和初始数据注入模板</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    htmlTemplate </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> htmlTemplate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">replace</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'&lt;div id="root"&gt;&lt;/div&gt;'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 找到 React 的挂载点</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">&lt;div id="root"&gt;</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">appHtml</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">&lt;/div&gt;</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 替换为服务端渲染的内容</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">replace</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">'&lt;/body&gt;'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 将初始数据挂载到 window；生产环境务必做 HTML 安全序列化，避免 &lt;/script&gt; 等 XSS</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">&lt;script&gt;window.__INITIAL_DATA__ = </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation constant" style="color:#36acaa">JSON</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation function" style="color:#d73a49">stringify</span><span class="token template-string interpolation punctuation" style="color:#393A34">(</span><span class="token template-string interpolation">user</span><span class="token template-string interpolation punctuation" style="color:#393A34">)</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation function" style="color:#d73a49">replace</span><span class="token template-string interpolation punctuation" style="color:#393A34">(</span><span class="token template-string interpolation regex regex-delimiter" style="color:#36acaa">/</span><span class="token template-string interpolation regex regex-source language-regex" style="color:#36acaa">&lt;</span><span class="token template-string interpolation regex regex-delimiter" style="color:#36acaa">/</span><span class="token template-string interpolation regex regex-flags" style="color:#36acaa">g</span><span class="token template-string interpolation punctuation" style="color:#393A34">,</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation string" style="color:#e3116c">'\\u003c'</span><span class="token template-string interpolation punctuation" style="color:#393A34">)</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">;&lt;/script&gt;&lt;/body&gt;</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 步骤 5: 发送最终的完整 HTML 页面</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">htmlTemplate</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="4配置静态文件服务">4.配置静态文件服务<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#4%E9%85%8D%E7%BD%AE%E9%9D%99%E6%80%81%E6%96%87%E4%BB%B6%E6%9C%8D%E5%8A%A1" class="hash-link" aria-label="4.配置静态文件服务的直接链接" title="4.配置静态文件服务的直接链接" translate="no">​</a></h4>
<p>浏览器收到 <code>HTML</code> 后，还需要加载 <code>CSS</code> 和 <code>JavaScript</code> 文件。我们需要让 <code>Nest.js</code> 能够提供这些静态资源。</p>
<p>在 <code>main.ts</code> 中配置 <code>ServeStaticModule</code>：</p>
<div class="language-TypeScript language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// nestjs-project/src/main.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> NestFactory </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@nestjs/core'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AppModule </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./app.module'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> NestExpressApplication </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@nestjs/platform-express'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> join </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'path'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">bootstrap</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> NestFactory</span><span class="token punctuation" style="color:#393A34">.</span><span class="token generic-function function" style="color:#d73a49">create</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">NestExpressApplication</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">AppModule</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 配置静态资源目录，指向 React 项目的 build 产物目录</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">useStaticAssets</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'..'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react-build'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">bootstrap</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="前端适配从-render-到-hydrate">前端适配：从 <code>render</code> 到 <code>hydrate</code><a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#%E5%89%8D%E7%AB%AF%E9%80%82%E9%85%8D%E4%BB%8E-render-%E5%88%B0-hydrate" class="hash-link" aria-label="前端适配从-render-到-hydrate的直接链接" title="前端适配从-render-到-hydrate的直接链接" translate="no">​</a></h3>
<p>前端的改造相对简单，核心是修改 <code>React</code> 的入口文件，并确保组件能适应 <code>SSR</code> 和 <code>CSR</code> 两种场景。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="1入口文件改造拥抱-hydrateroot">1.入口文件改造：拥抱 <code>hydrateRoot</code><a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#1%E5%85%A5%E5%8F%A3%E6%96%87%E4%BB%B6%E6%94%B9%E9%80%A0%E6%8B%A5%E6%8A%B1-hydrateroot" class="hash-link" aria-label="1入口文件改造拥抱-hydrateroot的直接链接" title="1入口文件改造拥抱-hydrateroot的直接链接" translate="no">​</a></h4>
<p>这是最关键的一步。将 <code>ReactDOM.createRoot().render()</code> 替换为 <code>ReactDOM.hydrateRoot()</code>。</p>
<div class="language-JavaScript language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// react-project/src/index.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> createRoot</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> hydrateRoot </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react-dom/client'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">App</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./App'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> container </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token dom variable" style="color:#36acaa">document</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getElementById</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'root'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 仅当容器里已有服务端 HTML 时才 hydrate；空容器应 createRoot，否则容易 hydration mismatch</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">container</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">hasChildNodes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">hydrateRoot</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">container</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">App</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">createRoot</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">container</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">render</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">App</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>深入探讨：能不能一律用 <code>hydrateRoot</code>？</strong></p>
<blockquote>
<p><strong>不能想当然。</strong> <code>hydrateRoot</code> 假定 DOM 已与服务端渲染结果对齐。若容器为空（纯 CSR 首屏），正确做法是 <code>createRoot().render</code>。混用时按“是否已有 SSR HTML”分支，或保证所有入口都先产出可对齐的 HTML。</p>
</blockquote>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="2同构组件的设计">2.“同构组件”的设计<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#2%E5%90%8C%E6%9E%84%E7%BB%84%E4%BB%B6%E7%9A%84%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="2.“同构组件”的设计的直接链接" title="2.“同构组件”的设计的直接链接" translate="no">​</a></h4>
<p>我们的 <code>UserPage</code> 组件需要能同时在服务端和客户端两种环境下工作。</p>
<div class="language-JavaScript language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// react-project/src/pages/UserPage.jsx</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function maybe-class-name" style="color:#d73a49">UserPage</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter punctuation" style="color:#393A34">{</span><span class="token parameter"> initialUser </span><span class="token parameter punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 优先使用服务端注入的数据，如果没有，则为 undefined</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setUser</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">useState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">initialUser</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 这个 effect 只会在客户端执行</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">useEffect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 如果没有初始数据 (例如通过客户端路由跳转而来)，则在客户端获取</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'在客户端获取数据...'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// fetch('/api/user/123').then(res =&gt; res.json()).then(data =&gt; setUser(data));</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 依赖 user 状态</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">div</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token maybe-class-name">Loading</span><span class="token spread operator" style="color:#393A34">...</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">div</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">div</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">h1</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain">用户资料</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">h1</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">p</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">strong</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token constant" style="color:#36acaa">ID</span><span class="token operator" style="color:#393A34">:</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">strong</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">p</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">p</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">strong</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain">姓名</span><span class="token operator" style="color:#393A34">:</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">strong</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">p</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">p</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">strong</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain">邮箱</span><span class="token operator" style="color:#393A34">:</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">strong</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">email</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">p</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">div</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token maybe-class-name">UserPage</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>深入探讨：为什么 <code>useEffect</code> 可以存在于 <code>SSR</code> 组件中？</strong></p>
<blockquote>
<p>这是一个常见的困惑。核心在于：服务端的 ReactDOMServer.renderToString 会完全忽略 useEffect 及其内部逻辑。</p>
<p>在服务端：renderToString 只进行一次同步渲染，不触发任何组件挂载后的生命周期。它只关心如何根据传入的 props (initialUser) 生成 HTML。</p>
<p>在客户端：组件挂载后，useEffect 会正常执行。此时，它扮演了“后备方案”的角色：如果 initialUser 存在，if 条件不成立，什么也不做；如果 initialUser 不存在（意味着这是一次客户端导航），它就会触发 fetch 请求去获取数据。</p>
<p>这种一套代码、两种行为的组件，正是“同构 (Isomorphic)”的精髓。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="注意事项">注意事项<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" class="hash-link" aria-label="注意事项的直接链接" title="注意事项的直接链接" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="1如何强制-user-路由走服务端渲染">1.如何强制 /user 路由走服务端渲染？<a href="https://docs.asmu1n.com/blog/2025/07/03/understand-ssr-hybrid#1%E5%A6%82%E4%BD%95%E5%BC%BA%E5%88%B6-user-%E8%B7%AF%E7%94%B1%E8%B5%B0%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%B8%B2%E6%9F%93" class="hash-link" aria-label="1.如何强制 /user 路由走服务端渲染？的直接链接" title="1.如何强制 /user 路由走服务端渲染？的直接链接" translate="no">​</a></h4>
<p>在你的应用中，如何链接到 /user 页面决定了它的渲染方式：</p>
<p>使用 <code>&lt;Link to="/user"&gt;</code> (from react-router-dom)：会触发客户端路由，渲染流程完全在浏览器内完成，整个过程不会请求服务器 <code>Nest.js</code> 的 <code>SSR</code> 接口。</p>
<p>使用 <code>&lt;a href="/user"&gt;</code>(标准 HTML 标签)：会触发整页刷新，浏览器向服务器发起一个新的 <code>GET</code> 请求，从而命中我们的 <code>SSR Controller</code>，得到一份服务端渲染的页面。</p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="React" term="React"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[模式匹配：更具阅读性的条件分支]]></title>
        <id>https://docs.asmu1n.com/blog/2025/04/29/match</id>
        <link href="https://docs.asmu1n.com/blog/2025/04/29/match"/>
        <updated>2025-04-29T20:32:00.000Z</updated>
        <summary type="html"><![CDATA[面临多分支判断，如果面对结构数据只能 if，else。switch 无法很好地解决。同时，大量的 if，else]]></summary>
        <content type="html"><![CDATA[<p>面临多分支判断，如果面对结构数据只能 if，else。switch 无法很好地解决。同时，大量的 if，else
判断会让其条件判断分支没有那么直观，尤其是复杂数据中多个属性进行层级、通配符判断。本文提出基于函数链式判断进行条件分支管理。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://docs.asmu1n.com/blog/2025/04/29/match#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>首先先指明工具函数要解决的问题。</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">UserStatus</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'active'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'inactive'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'pending'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">UserRole</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'editor'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'guest'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">User</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> UserStatus</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  role</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> UserRole</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  isVerified</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> User </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'active'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> role</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isVerified</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>面对这样的数据结构，我们进行条件分支判断往往会去使用if，else 去进行判断</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">role</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">status</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">status</span><span class="token operator" style="color:#393A34">===</span><span class="token string" style="color:#e3116c">'pending'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">role</span><span class="token operator" style="color:#393A34">===</span><span class="token string" style="color:#e3116c">'admin'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token operator" style="color:#393A34">...</span><span class="token operator" style="color:#393A34">...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">role</span><span class="token operator" style="color:#393A34">===</span><span class="token string" style="color:#e3116c">'guest'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token operator" style="color:#393A34">...</span><span class="token operator" style="color:#393A34">...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">status</span><span class="token operator" style="color:#393A34">===</span><span class="token string" style="color:#e3116c">'active'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">...</span><span class="token operator" style="color:#393A34">...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">...</span><span class="token operator" style="color:#393A34">...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// switch 不能直接对结构数据进行分支判断</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">switch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">role</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">switch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">status</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'pending'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token operator" style="color:#393A34">...</span><span class="token operator" style="color:#393A34">...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>这样随着层级越来越复杂，判断条件越来越不直观。尤其是面对一些迭代频繁且时间跨度特别长的项目来说很容易变成屎山代码。</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user1</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> User </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'active'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> role</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isVerified</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user2</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> User </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'pending'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> role</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'guest'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isVerified</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user3</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> User </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'inactive'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> role</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'editor'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isVerified</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">match</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'active'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> role</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'【管理员】欢迎回来！'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'active'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'【用户】欢迎您！'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'pending'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'您正在审核中，请稍候…'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'未识别的状态'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 输出: 【管理员】欢迎回来！</span><br></div></code></pre></div></div>
<p>这其实就是本方法想实现的效果，直接声明定义判定条件的数据属性，同时借助<code>_</code>去声明一个通配符，去进行宽松判断以及声明默认分支。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="源码">源码<a href="https://docs.asmu1n.com/blog/2025/04/29/match#%E6%BA%90%E7%A0%81" class="hash-link" aria-label="源码的直接链接" title="源码的直接链接" translate="no">​</a></h2>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> * 否定模式</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">Not</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">not</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Not</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Not</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> * 或模式</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic"> */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">Or</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> patterns</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Pattern</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">or</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">...</span><span class="token plain">patterns</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Pattern</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Or</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Or</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">patterns</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 通配符占位</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> _ </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">Symbol</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'wildcard'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">Pattern</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> _ </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> Not</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">Pattern</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> Or</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">Pattern</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">Matcher</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Pattern</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">handler</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Matcher</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> matched </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">matched</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">handler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 匹配成功后终止链式调用</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 继续链式调用</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pattern</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Pattern</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token builtin">any</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 通配符匹配</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> _</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 处理 Not 包装类型</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Not</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> negatedValue </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> negatedValue</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 处理 Or 包装类型</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Or</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">patterns</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">some</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 如果是函数，执行谓词</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> pattern </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'function'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 判断是否为基本类型（string / number / boolean）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> isPrimitive </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'string'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'number'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'boolean'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> value </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> value </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Number</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> value </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Boolean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isPrimitive</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 原始值直接比较</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token keyword" style="color:#00009f">is</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 判断是否为数组</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">Array</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">isArray</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token builtin">Array</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">isArray</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">length </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> value</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">length</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 尝试匹配数组中的每个元素</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">length</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i</span><span class="token operator" style="color:#393A34">++</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">i</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">i</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 对象匹配（部分匹配）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> pattern </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'object'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> pattern </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> value </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'object'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> value </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">entries</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">every</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">key</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> val</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> key </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> value </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">key</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> val</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">match</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Matcher</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Matcher</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="解析">解析<a href="https://docs.asmu1n.com/blog/2025/04/29/match#%E8%A7%A3%E6%9E%90" class="hash-link" aria-label="解析的直接链接" title="解析的直接链接" translate="no">​</a></h3>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">Matcher</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Pattern</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">handler</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Matcher</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> matched </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">matched</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">handler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 匹配成功后终止链式调用</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 继续链式调用</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>Matcher</code>构造函数接受原始数据，并链式调用<code>on</code>进行条件判断，一旦匹配成功就终止，否则返回自身准备下一次判断。</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pattern</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Pattern</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token builtin">any</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 通配符匹配</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> _</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 处理 Not 包装类型</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Not</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> negatedValue </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> negatedValue</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 处理 Or 包装类型</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Or</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">patterns</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">some</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 如果是函数，执行谓词</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> pattern </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'function'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 判断是否为基本类型（string / number / boolean）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> isPrimitive </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'string'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'number'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'boolean'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> value </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> value </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Number</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> value </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Boolean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isPrimitive</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 原始值直接比较</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token keyword" style="color:#00009f">is</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 判断是否为数组</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">Array</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">isArray</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token builtin">Array</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">isArray</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">length </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> value</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">length</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 尝试匹配数组中的每个元素</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">length</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i</span><span class="token operator" style="color:#393A34">++</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">i</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pattern</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">i</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 对象匹配（部分匹配）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> pattern </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'object'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> pattern </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> value </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'object'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> value </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">entries</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pattern</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">every</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">key</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> val</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> key </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> value </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">matchesPattern</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">key</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> val</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>matchesPattern</code>判断函数，采用递归<code>声明的数据模式</code>判定是否与<code>接收的数据</code>匹配。正常情况下，根据所声明的<code>数据模式</code>，判断每一个属性或者值是否都符合，然后进入相应的分支。</p>
<p>当然，除此之外还声明了<code>Not</code>，<code>Or</code>类去转换判断逻辑，在<code>matchesPattern</code>中通过<code>instanceof</code>从原型链上判断是否为<code>工具类</code>。</p>
<p>同时，还提前定义<code>_</code>作为通配符，作为宽松判断以及默认分支处理。以及<code>自定义回调函数</code>处理判断逻辑，基本数据类型直接通过<code>Object.is</code>进行判断。</p>
<p>而数组或者元组以及对象类型则采取部分匹配策略，即不严格判断<strong>完全相等</strong>而是只要全部符合<strong>声明的值</strong>（<em>注意数组的情况下，接收的数组长度要不少于<code>pattern</code>的长度，不然缺少<code>pattern</code>判断的数据应该判断不符合</em>）</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> student </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  age</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">18</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">'john'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  sex</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">'male'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">match</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">student</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">age</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">18</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">sex</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">'male'</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//成功输出 ‘john’， 不会严格限制必须为    {age:18,sex:'male'}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> list </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">match</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//没有成功输出，虽然`_`作为通配符，通过了第一个和第二个。但是没有第三个值，所以判断不符合，没有符合的分支。</span><br></div></code></pre></div></div>
<p>同时，针对数组和对象数据采取递归处理进行判断，一旦有不符合的就提前退出了。除此之外借助<code>not</code>，<code>or</code>进一步处理判断逻辑。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="注意点">注意点<a href="https://docs.asmu1n.com/blog/2025/04/29/match#%E6%B3%A8%E6%84%8F%E7%82%B9" class="hash-link" aria-label="注意点的直接链接" title="注意点的直接链接" translate="no">​</a></h2>
<p>1.链式调用需要注意顺序，<code>模式匹配</code>是一次次遍历执行的，一旦中间有符合的直接进入该分支，不符合的才会进行下一次条件判断。</p>
<p>2.最多只能进入一条分支，不存在同时匹配多项进行多条路线（注意优先顺序）</p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="TypeScript" term="TypeScript"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[双Token认证方案]]></title>
        <id>https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken</id>
        <link href="https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken"/>
        <updated>2025-03-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[使用双重Token验证的用户验证方案。]]></summary>
        <content type="html"><![CDATA[<p>使用双重<code>Token</code>验证的用户验证方案。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>在之前的项目中，我基本都是围绕<code>JWT</code>去做用户校验。<code>JWT</code>天然的无状态方案不需要我们专门在服务端去维护<code>Session</code>表，也不需要我们去做<code>Session</code>的同步，只需要在客户端存储<code>Token</code>，然后在请求时携带<code>Token</code>即可，相当的简便。</p>
<p>但这带来一个问题，在<code>JWT</code>中，我们无法在服务端主动去<code>销毁Token</code>，因为<code>Token</code>是无状态的，无法在服务端去维护<code>Token</code>的状态。即使用户在客户端进行了登出操作，但如果拿到之前的<code>Token</code>放到请求头依然能够正常请求。虽说这对于一般的用户进行正常操作来说是没有什么影响的，但是在浏览器<code>Token</code>是完全暴露的情况下，这就会带来一个安全问题。尤其是简单地使用<code>JWT</code>作为登陆凭证往往会设置较长的过期时间，那么在这段时间内，一旦有人窃取拿到了<code>Token</code>，就可以一直使用这个<code>Token</code>进行操作，这就会带来很大的安全隐患。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="解决思路">解决思路<a href="https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken#%E8%A7%A3%E5%86%B3%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="解决思路的直接链接" title="解决思路的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用refresh-token">使用<code>Refresh Token</code><a href="https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken#%E4%BD%BF%E7%94%A8refresh-token" class="hash-link" aria-label="使用refresh-token的直接链接" title="使用refresh-token的直接链接" translate="no">​</a></h3>
<p>首先是<code>JWT</code>一般过期时间都比较长的问题，可以通过缩短<code>Token</code>的过期时间来解决。但这又带来了新问题：<code>Token</code>过期后用户需要频繁重新登录，体验很差。</p>
<p>这时候可以用两个<code>Token</code>解决：一个有效期短的<code>Access Token</code>作为登录凭证，一个有效期长的<code>Refresh Token</code>作为刷新凭证。登录成功后服务端返回两者；当 Access Token 过期时，客户端用 Refresh Token 换取新的 Access Token（可选是否同时轮转 Refresh Token）。下面代码示例采用「只刷新 Access Token、Refresh Token 保持不变直到登出或过期」的简化方案。</p>
<p>这样即使真正的<code>验证Token</code>有效期短，也可以通过长时效的<code>Refresh Token</code>来无感刷新<code>验证Token</code>，无需用户重新登陆。而且由于真正的<code>验证Token</code>有效期短，大大缩短了使用非法获取的<code>Token</code>进行非法操作的窗口时间。</p>
<ul>
<li class="">
<p><code>刷新Token</code> 有效期长，用于刷新<code>验证Token</code>  由服务端通过<code>httponly-cookie</code>回传给客户端</p>
</li>
<li class="">
<p><code>验证Token</code> 有效期短，用于验证用户身份  由服务端通过<code>JSON</code>数据回传，由客户端存储在<code>sessionStorage</code>或者直接存储在运行内存中</p>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="黑名单">黑名单<a href="https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken#%E9%BB%91%E5%90%8D%E5%8D%95" class="hash-link" aria-label="黑名单的直接链接" title="黑名单的直接链接" translate="no">​</a></h3>
<p>维护一个黑名单，用于存储主动失效的<code>刷新Token</code>。</p>
<p>在用户登出时，服务端将用户的<code>刷新Token</code>加入到黑名单中并清除其对应的<code>cookie</code>，这样用户不得不重新登录。即便用户通过某种手段使用之前的<code>刷新Token</code>去获取新的<code>验证Token</code>，也会因为黑名单的存在而无法获取到新的<code>验证Token</code>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="代码示例">代码示例<a href="https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken#%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" class="hash-link" aria-label="代码示例的直接链接" title="代码示例的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="前端部分">前端部分<a href="https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken#%E5%89%8D%E7%AB%AF%E9%83%A8%E5%88%86" class="hash-link" aria-label="前端部分的直接链接" title="前端部分的直接链接" translate="no">​</a></h3>
<div class="language-TS language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> axiosInstance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> axios</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    baseURL</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/api'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//无需任何处理的接口</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> noCheckRequestList</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'/auth/register'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/auth/login'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/auth/refresh-accessToken'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/auth/logout'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//请求处理</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">axiosInstance</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">interceptors</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> InternalAxiosRequestConfig </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain"> IRequestConfig</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 不进行任何处理的接口</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">noCheckRequestList</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">url </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 处理验证token</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">accessToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">refreshAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'Authorization'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Bearer </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">accessToken</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//响应处理</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">axiosInstance</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">interceptors</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 响应成功回调</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">response</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> IResponseParams</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">IResponse</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 服务端成功响应了数据,但是业务结果是失败的</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">success</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> toastError </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">toastError </span><span class="token operator" style="color:#393A34">??</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">toastError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                toast</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><br></div></code></pre></div></div>
<p>以<code>axois</code>封装网络请求举例，在接口请求前，我们需要将<code>验证Token</code>放入自定义请求头中。</p>
<p>同时我们还需要区分哪些接口是不需要这些处理。常规的比如登录、接口一般不需要我们去做用户登录状态拦截。</p>
<p>响应成功的回调(状态码:<code>2XX</code>)就没什么好说的，很常规的业务错误判断以及错误信息提示。</p>
<div class="language-TS language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">//响应失败回调</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> error </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> originalRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> error</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 判断是否因为token过期导致失败（还要判断是否为重试请求以及是否需要进行token检查）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">response</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">status </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">401</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">originalRequest</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">_retry </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">noCheckRequestList</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">originalRequest</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">url </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">//标记为重试请求（再失败直接判断非法错误）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            originalRequest</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">_retry </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> newAccessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">refreshAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            originalRequest</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'Authorization'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Bearer </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">newAccessToken</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">axiosInstance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">originalRequest</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>响应失败回调就需要我们注意了，因为我要在这里去判断<code>失败原因</code>以及是否为<code>重试请求</code></p>
<p>假设一个服务器返回了失败响应，我们首先判断是不是<code>401</code>(认证错误)，以及这次请求是否已经被标记为<code>重试请求</code>(<code>_retry</code>)，同时还需要判断响应错误的接口是否在无需处理的列表中。</p>
<p>通过这些判断去分析这个请求是否在<code>刷新Token没有过期的情况下，没有验证Token或者验证Token过期的情况下导致响应失败</code></p>
<p>如果真是这样导致请求失败，我们将该请求标识为<code>重试请求</code>，同时重新获取<code>验证Token</code>(<code>refreshAccessToken</code>)并放到请求头上重新请求。</p>
<p>如果还是失败，那我们可以认为服务端出现了问题，或者该用户的信息出现异常导致了失败。不再重新尝试请求。</p>
<div class="language-TS language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 刷新accessToken</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">refreshAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> response </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> axiosInstance</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'/auth/refresh-accessToken'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">accessToken </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">status </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">401</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">logout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">saveAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">accessToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> accessToken </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// refreshToken cookie 过期了，直接注销重新登录</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        toast</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'登录信息已过期，请重新登录'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">removeAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        window</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">location</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">href </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/login'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>这是获取<code>验证Token</code>的代码。</p>
<p>由于我们的<code>刷新Token</code>在<code>cookie</code>上，所以我们不需要像<code>验证Token</code>一样自己放入到自定义请求头中。</p>
<p>如果服务端返回错误响应或者没有有效的<code>刷新Token</code>，我们就认为现在的<code>刷新Token</code>已经过期了或者不存在。手动执行注销操作，清除客户端的所有验证信息。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="服务端部分">服务端部分<a href="https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken#%E6%9C%8D%E5%8A%A1%E7%AB%AF%E9%83%A8%E5%88%86" class="hash-link" aria-label="服务端部分的直接链接" title="服务端部分的直接链接" translate="no">​</a></h3>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">//注册</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> register </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">RequestHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> name</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> email</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> password </span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> email</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> password</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">findUser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> email </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">UnauthorizedError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'邮箱已注册'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> passwordHash </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> bcrypt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">hash</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">password</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">addUser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> name</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> email</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> passwordHash </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">responseBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'注册成功'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 登录</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> login </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">RequestHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> email</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> password </span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> email</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> password</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">findUser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> email </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">user </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> bcrypt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">compare</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">password</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">passwordHash</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">UnauthorizedError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'用户名或密码错误'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> payload</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> UserPayload </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">name</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        email</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">email</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        avatarUrl</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">avatarUrl</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        roles</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">roles</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">payload</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> refreshToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateRefreshToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">payload</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">cookie</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'refresh_token'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> refreshToken</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> httpOnly</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> secure</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sameSite</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'strict'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">responseBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'获取Token'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> accessToken</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 登出</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> logout </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">RequestHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> refreshToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cookies</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">refresh_token </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">refreshToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">UnauthorizedError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'无刷新令牌'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">clearCookie</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'refresh_token'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// JWT exp 通常是秒级时间戳；仅当 refresh token 尚未过期时才需要拉黑</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> expiresAt </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getJwtExpiry</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">refreshToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 建议返回毫秒时间戳</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">expiresAt </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> Date</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> expiresAt</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> parsedData </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> blackListInsertValidation</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">parse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            token</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> refreshToken</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            expiresAt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Date</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">expiresAt</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">insert</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">blackList</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">values</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parsedData</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">responseBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'退出成功'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>常规的登录注册，只不过<code>登录</code>需要生成两个 <code>token</code> 一个通过<code>cookie</code>回传给客户端，另一个直接通过<code>json</code>数据返回，由客户端去处理存储。</p>
<p><code>登出</code>操作先清除<code>refresh_token</code> cookie，再把<strong>尚未过期</strong>的 refresh token 写入黑名单——若 token 已过期，黑名单没有意义；若未过期却不拉黑，登出后仍可被拿去换新的 access token。</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 用户身份验证中间件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> authenticateToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">RequestHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> authHeader </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'authorization'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> token </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> authHeader </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> authHeader</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">split</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">' '</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">UnauthorizedError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'缺少认证令牌'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">verifyToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> UserPayload</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">validatorNoEmpty</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">UnauthorizedError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'无效的认证令牌'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 将用户信息附加到请求对象上</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 验证身份状态</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> validateAuth </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">RequestHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">user </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">email</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">UnauthorizedError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'无效的令牌'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">findUser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        email</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">email</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">UnauthorizedError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'用户不存在'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">responseBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'验证成功'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 刷新令牌</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> refreshToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">RequestHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> refreshToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cookies</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">refresh_token </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">refreshToken </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">isTokenBlacklisted</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">refreshToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">UnauthorizedError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'无效的刷新令牌'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> decodedToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">verifyToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">refreshToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> RefreshPayload</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">decodedToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">UnauthorizedError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'令牌过期'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 获取用户</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">findUser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> decodedToken</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">UnauthorizedError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'用户不存在'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> newAccessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">responseBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'刷新Token'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> newAccessToken </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>用户身份验证中间件的逻辑就是解析<code>jwt</code>载荷的用户信息，挂在<code>req</code>上，让后续的逻辑的直接使用上。</p>
<p><code>validateAuth</code>就是根据<code>jwt</code>信息检查是否存在该用户、验证其身份。</p>
<p><code>refreshToken</code> 就是通过验证后的<code>刷新Token</code>拿到信息去检索该用户，使用用户数据去重新生成一个新<code>Token</code></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="流程示意">流程示意<a href="https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken#%E6%B5%81%E7%A8%8B%E7%A4%BA%E6%84%8F" class="hash-link" aria-label="流程示意的直接链接" title="流程示意的直接链接" translate="no">​</a></h2>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">sequenceDiagram</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant C as 客户端</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant S as 服务端</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant BL as 黑名单</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    C-&gt;&gt;S: 登录</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    S--&gt;&gt;C: Access Token + Set-Cookie(Refresh Token)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    C-&gt;&gt;S: 业务请求 + Authorization Bearer Access</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    S--&gt;&gt;C: 200 / 401(过期)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    alt Access 过期</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        C-&gt;&gt;S: 刷新接口（Cookie 带 Refresh）</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        S-&gt;&gt;BL: 检查是否在黑名单</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        S--&gt;&gt;C: 新的 Access Token</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        C-&gt;&gt;S: 重试原请求</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    end</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    C-&gt;&gt;S: 登出</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    S-&gt;&gt;BL: 拉黑未过期的 Refresh Token</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    S--&gt;&gt;C: 清除 Cookie</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="思维导图">思维导图<a href="https://docs.asmu1n.com/blog/2025/03/17/Double-AuthToken#%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE" class="hash-link" aria-label="思维导图的直接链接" title="思维导图的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="双 Token 验证方案" src="https://docs.asmu1n.com/assets/images/doubleToken_auth-7f2606d215cf57042c5020a020680433.png" width="1033" height="740" class="img_ev3q"></p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="认证" term="认证"/>
        <category label="TypeScript" term="TypeScript"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[React高阶组件(TS类型推导)]]></title>
        <id>https://docs.asmu1n.com/blog/2025/02/27/React-HOC</id>
        <link href="https://docs.asmu1n.com/blog/2025/02/27/React-HOC"/>
        <updated>2025-02-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[React高阶组件]]></summary>
        <content type="html"><![CDATA[<p><code>React高阶组件</code></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://docs.asmu1n.com/blog/2025/02/27/React-HOC#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<ul>
<li class="">
<p><code>HOC高阶组件</code>本质上是一个函数，往往接收一个<code>函数组件</code>并返回一个新的组件，在这个新的组件中对传入的组件进行了一些额外的操作。（即对原来的的组件内容进行补充或者增强）</p>
</li>
<li class="">
<p>在开发场景中，<code>HOC高阶组件</code>往往充当着<code>工具组件</code>的角色，他并不<code>侵入性的影响</code>原来的组件。基于这个原则，在<code>HOC</code>中去组织一些复用性的代码逻辑，从而控制其<code>子树UI</code>的渲染。</p>
</li>
<li class="">
<p>与自定义<code>Hook</code>不同，<code>Hook</code>只能将我们的代码逻辑抽离到一个地方，但并不能直接影响<code>UI</code>的渲染，而是通过<code>Hook</code>提供的内容自己去控制视图内容。</p>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hoc的作用不考虑反向继承有侵入性影响"><code>HOC</code>的作用(不考虑反向继承，有侵入性影响)<a href="https://docs.asmu1n.com/blog/2025/02/27/React-HOC#hoc%E7%9A%84%E4%BD%9C%E7%94%A8%E4%B8%8D%E8%80%83%E8%99%91%E5%8F%8D%E5%90%91%E7%BB%A7%E6%89%BF%E6%9C%89%E4%BE%B5%E5%85%A5%E6%80%A7%E5%BD%B1%E5%93%8D" class="hash-link" aria-label="hoc的作用不考虑反向继承有侵入性影响的直接链接" title="hoc的作用不考虑反向继承有侵入性影响的直接链接" translate="no">​</a></h2>
<ol>
<li class="">负责补充传递<code>props</code>。</li>
<li class="">直接在<code>HOC</code>内部去控制<code>原有组件</code>的渲染(不改变<code>原有组件</code>的前提下)。</li>
</ol>
<p>二者并非互斥的关系，<code>HOC</code>的核心就是由自己内部执行特定逻辑从而影响、控制<code>原有组件</code>的内容。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="代码示例">代码示例<a href="https://docs.asmu1n.com/blog/2025/02/27/React-HOC#%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" class="hash-link" aria-label="代码示例的直接链接" title="代码示例的直接链接" translate="no">​</a></h2>
<p><strong>目标示例：</strong> 哨兵进入视口时通知子组件（常见于无限滚动「快到底了」）。注意：<code>isVisible</code> 表示<strong>是否可见</strong>，不等于业务上的 <code>isLoading</code>（是否在请求中）。下面用 <code>isVisible</code> 注入，避免语义混淆。</p>
<ol>
<li class="">补充 Props：</li>
</ol>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">ComponentType</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> useEffect</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> useMemo</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> useRef</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> useState </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">WithVisible</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> isVisible</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token maybe-class-name">ObserverHOC</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">P</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">WithVisible</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">Component</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">ComponentType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">P</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">WrappedComponent</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">props</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Omit</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">P</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'isVisible'</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> domRef </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">useRef</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">HTMLDivElement</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">isVisible</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setIsVisible</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> observer </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useMemo</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">IntersectionObserver</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">entries </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    entries</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">forEach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">entry </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                        </span><span class="token function" style="color:#d73a49">setIsVisible</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">entry</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">isIntersecting</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">useEffect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> dom </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> domRef</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">current</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">dom</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> observer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">observe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">dom</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                observer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">disconnect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">observer</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">ref</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">domRef</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">                </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Component</span><span class="token tag" style="color:#00009f"> </span><span class="token tag spread punctuation" style="color:#393A34">{</span><span class="token tag spread operator" style="color:#393A34">...</span><span class="token tag spread punctuation" style="color:#393A34">(</span><span class="token tag spread" style="color:#00009f">props </span><span class="token tag spread keyword" style="color:#00009f">as</span><span class="token tag spread" style="color:#00009f"> </span><span class="token tag spread constant" style="color:#36acaa">P</span><span class="token tag spread punctuation" style="color:#393A34">)</span><span class="token tag spread punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">isVisible</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">isVisible</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token maybe-class-name">WrappedComponent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">displayName</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">ObserverHOC(</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">Component</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">displayName </span><span class="token template-string interpolation operator" style="color:#393A34">||</span><span class="token template-string interpolation"> Component</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">name </span><span class="token template-string interpolation operator" style="color:#393A34">||</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation string" style="color:#e3116c">'Component'</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">)</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token maybe-class-name">WrappedComponent</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token maybe-class-name">ObserverHOC</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><code>Omit&lt;P, 'isVisible'&gt;</code> 表示包装后的组件<strong>不必再手动传</strong> <code>isVisible</code>，由 HOC 注入。子组件内部仍可把 <code>isVisible</code> 当成「触发下一页加载」的信号，另用本地 state 管真正的 <code>isLoading</code>。</p>
<p><img decoding="async" loading="lazy" alt="React-HOC-1.png" src="https://docs.asmu1n.com/assets/images/React-HOC-1-80160fd44fccff30faa4000cf6a75ff0.png" width="1477" height="460" class="img_ev3q"></p>
<p>如果你漏传了<code>Props</code>，react也能提供相应的类型提示。</p>
<p><img decoding="async" loading="lazy" alt="React-HOC-2.png" src="https://docs.asmu1n.com/assets/images/React-HOC-2-ff8807962d4020ef3f343c5d34a2c618.png" width="1530" height="604" class="img_ev3q"></p>
<p>当然，这样需要你确定<code>HOC</code>本身传递的<code>Props</code>的类型和字段，保证其被包装的组件能够正确接收到。</p>
<ol start="2">
<li class="">控制<code>原有组件</code>的渲染(不改变<code>原有组件</code>的前提下)：</li>
</ol>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token maybe-class-name">ObserverMountHOC</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">P</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">object</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">Component</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">ComponentType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">P</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">WrappedComponent</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">props</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">P</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> domRef </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">useRef</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">HTMLDivElement</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">isVisible</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setIsVisible</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> observer </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useMemo</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">IntersectionObserver</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">entries </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            entries</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">forEach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">entry </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setIsVisible</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">entry</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">isIntersecting</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">useEffect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> dom </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> domRef</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">current</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">dom</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> observer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">observe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">dom</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> observer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">disconnect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">observer</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">ref</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">domRef</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">isVisible </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Component</span><span class="token tag" style="color:#00009f"> </span><span class="token tag spread punctuation" style="color:#393A34">{</span><span class="token tag spread operator" style="color:#393A34">...</span><span class="token tag spread" style="color:#00009f">props</span><span class="token tag spread punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token maybe-class-name">WrappedComponent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">displayName</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">ObserverMountHOC(</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">Component</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">displayName </span><span class="token template-string interpolation operator" style="color:#393A34">||</span><span class="token template-string interpolation"> Component</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">name </span><span class="token template-string interpolation operator" style="color:#393A34">||</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation string" style="color:#e3116c">'Component'</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">)</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token maybe-class-name">WrappedComponent</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>这个例子 HOC 不改动子组件内部，只在 <code>isVisible</code> 为真时挂载子树（懒展示）。</p>
<p><img decoding="async" loading="lazy" alt="React-HOC-3.png" src="https://docs.asmu1n.com/assets/images/React-HOC-3-4bafc877b025779e6ceca67307b8b4be.png" width="1550" height="584" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="总结">总结<a href="https://docs.asmu1n.com/blog/2025/02/27/React-HOC#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<ul>
<li class="">用 <code>ComponentType&lt;P&gt;</code> 接住原组件 props，返回组件上用 <code>Omit</code> 去掉由 HOC 注入的字段</li>
<li class="">清理 <code>IntersectionObserver</code> 时优先 <code>disconnect()</code></li>
<li class="">可见性 ≠ 加载中：命名分开，业务状态仍放在子组件或数据层</li>
</ul>
<p>现代代码里横切逻辑也常用 Hook；HOC 仍适合「包一层 UI / 统一注入 props」的场景。</p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="React" term="React"/>
        <category label="TypeScript" term="TypeScript"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[用户登录方案]]></title>
        <id>https://docs.asmu1n.com/blog/2025/02/14/Auth</id>
        <link href="https://docs.asmu1n.com/blog/2025/02/14/Auth"/>
        <updated>2025-02-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[分析用户登录的过程。]]></summary>
        <content type="html"><![CDATA[<p>分析用户登录的过程。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cookie">cookie<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#cookie" class="hash-link" aria-label="cookie的直接链接" title="cookie的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="简介">简介<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E7%AE%80%E4%BB%8B" class="hash-link" aria-label="简介的直接链接" title="简介的直接链接" translate="no">​</a></h3>
<p><code>cookie</code>本质上只是存储在浏览器中的一小段数据片段，由服务端通过<code>Set-Cookie</code>响应头进行设置。<strong>默认同站请求</strong>时浏览器会自动携带；是否跨站发送则受 <code>SameSite</code>、<code>Secure</code>、域名与 CORS <code>credentials</code> 等策略约束，并不是绝对“不能跨域”。通过 <code>Domain</code> 可以在同一注册域下的子域间共享（仍受浏览器策略限制）。<code>cookie</code> 由浏览器管理：设置了过期时间则到期清除；未设置 <code>Max-Age</code>/<code>Expires</code> 的会话 cookie 通常在浏览器会话结束时清除。大小一般限制在约 4KB 量级。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="用户认证过程">用户认证过程<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E7%94%A8%E6%88%B7%E8%AE%A4%E8%AF%81%E8%BF%87%E7%A8%8B" class="hash-link" aria-label="用户认证过程的直接链接" title="用户认证过程的直接链接" translate="no">​</a></h3>
<ol>
<li class="">服务端在用户登录成功后，将用户认证信息放入在<code>cookie</code>中并设置过期时间通过<code>set-cookie</code>传回给客户端。</li>
<li class="">在<code>cookie</code>有效期间，客户端每次请求服务端都会携带<code>cookie</code>，并通过里面的信息完成用户认证。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="缺点">缺点<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E7%BC%BA%E7%82%B9" class="hash-link" aria-label="缺点的直接链接" title="缺点的直接链接" translate="no">​</a></h3>
<ol>
<li class="">若把敏感信息直接塞进 cookie，用户仍可通过开发者工具看到内容（<code>HttpOnly</code> 只能阻止 JS 读取，挡不住肉眼与部分攻击面）。</li>
<li class="">大小有限，不适合塞大量数据。</li>
<li class=""><strong>若 cookie 里直接存的是无状态凭证本身</strong>（例如长寿命 JWT），服务端很难单独吊销；若 cookie 只存 <code>sessionId</code>，删服务端 session 即可失效。也可用 <code>Set-Cookie</code> 清空 cookie。</li>
<li class="">跨站携带受浏览器策略限制，前后端跨域场景要显式设计 <code>SameSite</code> / CORS，不能假设“自动带上”。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="总结">总结<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h3>
<p>单纯采用<code>cookie</code>进行用户认证有着诸多缺点，一般都是在<code>session</code>和<code>token</code>方案将<code>cookie</code>作为传递凭证的媒介。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="session">session<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#session" class="hash-link" aria-label="session的直接链接" title="session的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="简介以及认证过程">简介以及认证过程<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E7%AE%80%E4%BB%8B%E4%BB%A5%E5%8F%8A%E8%AE%A4%E8%AF%81%E8%BF%87%E7%A8%8B" class="hash-link" aria-label="简介以及认证过程的直接链接" title="简介以及认证过程的直接链接" translate="no">​</a></h3>
<p><code>session</code>是存储在服务端的用户会话数据，在用户登录成功后，生成一个唯一的<code>sessionId</code>去映射指定的用户信息,并将<code>sessionId</code>通过<code>cookie</code>存储在客户端中,后续客户端的请求自动带上<code>cookie</code>中的<code>sessionId</code>，服务端通过<code>sessionId</code>找到指定的用户信息完成用户的认证。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="优点">优点<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E4%BC%98%E7%82%B9" class="hash-link" aria-label="优点的直接链接" title="优点的直接链接" translate="no">​</a></h3>
<ol>
<li class=""><code>session</code>由服务端自己进行存储和管理，这也就意味着服务端对用户的登录状态有绝对的控制权。能够通过删除会话记录使用户的登录凭证无效。</li>
<li class=""><code>session</code>存储在服务端，不会受到<code>cookie</code>大小的限制，能够存储较大的数据量。同时只将<code>sessionId</code>传回给客户端,也避免了关键信息的泄漏。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="缺点-1">缺点<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E7%BC%BA%E7%82%B9-1" class="hash-link" aria-label="缺点的直接链接" title="缺点的直接链接" translate="no">​</a></h3>
<ol>
<li class="">由于<code>session</code>存储在服务端中，服务器需要花费存储资源去存储、维护用户的会话数据。</li>
<li class="">在项目比较庞大，需要使用多台服务器去均衡负载，<code>session</code>的管理会比较复杂，需要在多台服务器之间进行同步。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tokenjwt">token(JWT)<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#tokenjwt" class="hash-link" aria-label="token(JWT)的直接链接" title="token(JWT)的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="简介-1">简介<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E7%AE%80%E4%BB%8B-1" class="hash-link" aria-label="简介的直接链接" title="简介的直接链接" translate="no">​</a></h3>
<p>常见的 <code>JWT</code>（JSON Web Token）默认是 <strong>JWS：经签名的、可被解码查看的载荷</strong>，<strong>不等于加密</strong>。签名用于防篡改；若需要保密载荷内容，应使用 JWE 或把敏感数据放在服务端。Token 由服务端签发，客户端携带，服务端校验签名与声明后完成认证。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="认证过程">认证过程<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E8%AE%A4%E8%AF%81%E8%BF%87%E7%A8%8B" class="hash-link" aria-label="认证过程的直接链接" title="认证过程的直接链接" translate="no">​</a></h3>
<ol>
<li class="">用户登录成功后，服务端这边根据用户信息去签发一个<code>JWT</code>，并将其返回给客户端。</li>
<li class="">客户端拿到<code>JWT</code>，可存于内存、<code>sessionStorage</code>/<code>localStorage</code>，或通过 <code>HttpOnly</code> cookie 下发。跨域场景更常把 Access Token 放请求头；cookie 方案则需配合 CORS 与 <code>SameSite</code> 设计。</li>
<li class="">服务端拿到<code>JWT</code>后，解析其中的内容，完成用户的认证。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="优点-1">优点<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E4%BC%98%E7%82%B9-1" class="hash-link" aria-label="优点的直接链接" title="优点的直接链接" translate="no">​</a></h3>
<ol>
<li class=""><code>JWT</code>由服务端进行签发和解析，不需要对用户的会话记录进行存储，减轻服务端的存储压力。并且能够在多台服务器之间保持登录的一致性。</li>
<li class=""><code>JWT</code>本身并不依赖于<code>cookie</code>,针对项目涉及跨域的请求，可以采取将签发的<code>JWT</code>存储在<code>localStorage</code>中，由前端控制把<code>JWT</code>放入到自定义请求头中,从而绕过<code>cookie</code>跨域的限制。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="缺点-2">缺点<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E7%BC%BA%E7%82%B9-2" class="hash-link" aria-label="缺点的直接链接" title="缺点的直接链接" translate="no">​</a></h3>
<ol>
<li class=""><code>JWT</code>本身经过签名，存储的信息有限。</li>
<li class=""><code>JWT</code>由服务端进行签发和解析，这个过程会增加服务端的计算压力。</li>
<li class=""><code>JWT</code>一旦签发成功，只能等过期自动失效，服务端不能控制<code>JWT</code>是否失效。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="magic-links魔法链接">Magic Links（魔法链接）<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#magic-links%E9%AD%94%E6%B3%95%E9%93%BE%E6%8E%A5" class="hash-link" aria-label="Magic Links（魔法链接）的直接链接" title="Magic Links（魔法链接）的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="简介-2">简介<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E7%AE%80%E4%BB%8B-2" class="hash-link" aria-label="简介的直接链接" title="简介的直接链接" translate="no">​</a></h3>
<p><code>Magic Links</code>旨在提供一种无需提供密码即可完成的模式，类似的还有手机号获取验证码，不过表现和体验上不一样。一般通过邮件发送登录链接，用户只需点击链接即可完成登录并跳转到相关页面。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="认证过程-1">认证过程<a href="https://docs.asmu1n.com/blog/2025/02/14/Auth#%E8%AE%A4%E8%AF%81%E8%BF%87%E7%A8%8B-1" class="hash-link" aria-label="认证过程的直接链接" title="认证过程的直接链接" translate="no">​</a></h3>
<p>用户输入邮箱发送登录请求，而服务端这边首先校验用户邮箱是否存在，然后生成一个唯一的<code>token</code>并结合邮箱拼接成一个登录链接。而<code>token</code>则需要服务端通过数据库去管理和维护，并设置一个较短的有效期。在用户使用登录链接完成登录后，<code>token</code>就会立即无效化。
而在用户点击登录链接进行登录时，本质上<code>token</code>就相当于短时间内自动过期的一次性密码，通过邮箱和一次性密码完成登录。</p>
<p><img decoding="async" loading="lazy" alt="token的数据库结构" src="https://docs.asmu1n.com/assets/images/magicLinksToken_Schema-0eed334df081f853d8330c494eb1b682.png" width="1180" height="325" class="img_ev3q"></p>
<ol>
<li class="">
<p><code>identifier</code>： <code>token</code>的验证账号信息，如果使用的是邮箱验证登录，那这个地方的值就是邮箱地址，如果为手机号验证码登录，那这个地方就是手机号</p>
</li>
<li class="">
<p><code>token</code>： 生成的<code>token</code>，只能用于与之匹配的<code>identifier</code></p>
</li>
<li class="">
<p><code>expires</code>：创建时一般默认比较短的有效期，且当用户使用<code>token</code>登录成功后立即过期无效化</p>
</li>
</ol>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="数据库" term="数据库"/>
        <category label="认证" term="认证"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[React 合成事件]]></title>
        <id>https://docs.asmu1n.com/blog/2025/02/06/React-Event</id>
        <link href="https://docs.asmu1n.com/blog/2025/02/06/React-Event"/>
        <updated>2025-02-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[React的事件系统]]></summary>
        <content type="html"><![CDATA[<p><code>React</code>的事件系统</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一图流">一图流<a href="https://docs.asmu1n.com/blog/2025/02/06/React-Event#%E4%B8%80%E5%9B%BE%E6%B5%81" class="hash-link" aria-label="一图流的直接链接" title="一图流的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="React事件系统" src="https://docs.asmu1n.com/assets/images/React%E4%BA%8B%E4%BB%B6%E7%B3%BB%E7%BB%9F-8905bc22700d5f8e58fe8367cd5c3e53.awebp" width="1512" height="916" class="img_ev3q">
<strong>Tip : React16及其之前的版本,React17以后已经删除<code>事件池</code></strong>
<a href="https://juejin.cn/post/6955636911214067720?searchId=2025020614074044F34CB4ABF07AD28EA1" target="_blank" rel="noopener noreferrer" class="">仅转载,图片来源:《「react进阶」一文吃透react事件系统原理》</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fiber">Fiber<a href="https://docs.asmu1n.com/blog/2025/02/06/React-Event#fiber" class="hash-link" aria-label="Fiber的直接链接" title="Fiber的直接链接" translate="no">​</a></h2>
<p><code>Fiber</code>在<code>React 16</code>之后引入，是可中断的协调（reconciliation）架构：把工作拆成可暂停的单元，以解决旧版递归同步更新阻塞主线程的问题。在事件流程中，<code>React</code>通过事件源对象找到对应<code>DOM</code>的<code>Fiber</code>节点，沿路径收集同类事件处理函数，从而模拟冒泡和捕获。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="react合成事件">React合成事件<a href="https://docs.asmu1n.com/blog/2025/02/06/React-Event#react%E5%90%88%E6%88%90%E4%BA%8B%E4%BB%B6" class="hash-link" aria-label="React合成事件的直接链接" title="React合成事件的直接链接" translate="no">​</a></h2>
<p>首先需要明确的是,<code>React</code>中我们所编写的类似下面的事件函数并非真正地绑定在<code>div</code>的<code>DOM</code>上,而是绑定在根节点<code>root</code>上。(<code>React16</code>及其之前的版本是将事件绑定在<code>document</code>上)</p>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">&lt;div onClick={()=&gt;{console.log('测试')}}&gt;测试</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="React事件注册" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAc4AAACOCAIAAAA3q38/AAAcGElEQVR4Ae2d729Vx5nH+7dcuafOsZNgkSwkJaEipEQ4spFNC91NvCGxI1VqRSAxJHip11gOXZvWJDikwcQoi0LsTWlpvaFpiRoqRCS0VFUWtLjrba3WUvyGN5F4w6us5scz88y5c6/P9b1z77mHL7ovxufMeWbmOzOfec5c7jxfa5f/zpT+pzK0t7c/sfO5QhTjAwWgABSAApUq8DWgtlLJkB8KQAEoUKkCQC38dCgABaBAcAWA2uASV7r6IT8UgAL5UwCoBWqhABSAAsEVAGqDS5y/9RktggJQoFIFgFqgFgpAASgQXIGwqH2qc8cv53/T3bur0hUA+aEAFIACeVIgLGqPHZ9aWl5ZWl6ZmJxqbV+XJ+HQFigABaBAegXConbyzZMKtUvLK1ev/XF7V08higfmF2/MX7y0vLJ06+L4/OLS8sqN+WFZ4+HZW4LL4nPr4oD8ucTA/OLS1RnVHp4uTF8zlunxuBDNCLPyoy8eunjj1sXZqytLy4uz0xdvMMvj4qL6XBuXZYkrVFYhEpW5NK1eK4rM4qccUAAKQIFKFAiL2p++8RbhTEDtr3//4of7XhHEXF6cPST5dXWGACrQZqBpqEd3BfJYenj21uLsocT2Cr9I6UMCrzfmhyVYr42LPwVYjX1tVpGd7gqy2zSZErLydKJ0/AkFoAAUKKlAWNRyr1Yx97e//wMRc+aSAK4B6MwlCUHtkE9fU44tZRYN4GmJTotmgqNxVEVC+KREzPGrMrP+0yUm5SlIBCtPVrre0tcWd4vMVrKa6RbhESgABe5hBcKiNuHVLi2vDI8eJWJWhVrFL2Fqmd765V6B2nawdCOMpkSt2JcQewiMxV6z9/CIsdpCBCgABVIrEBa13Kv981/+9urQcEtrewnU8g0EmxaZ7ds9UZU3z6JQ7EiYLQhNBD9qnQ0EvpkgISv3Geymrc8srwDSUAAKQIHVFAiL2p+8of8HwsefXN78xDbrigqQJbxa50st9vWU+Urq2rh2Od2ccheCgdW87Msvu0qgVn3rpfeR6Ss4Wz1uU+/bumZXk1XXB9mgABSAAlKBsKjdsXP35zcXfjQy1tLaDvpAASgABe5ZBcKi9p6VFQ2HAlAACnAFgNqS/zmDy4Q0FIACUKAaBYBaoBYKQAEoEFwBoDa4xNWshHgWCkCBfCgA1AK1UAAKQIHgCgC1wSXOx5qMVkABKFCNAkAtUAsFoAAUCK4AUBtc4mpWQjwLBaBAPhQAaoFaKAAFoEBwBYDa4BLnY01GK6AAFKhGAaAWqIUCUAAKBFcAqA0ucTUrIZ6FAlAgHwoAtUAtFIACUCC4AkBtQmJ9tGM+FlK0AgpAgYwoANQCtQkF8CcUgAK1V6D2qI0fXD/42uGRsX9LfHb90z+r5aWuEXPFobwipkPiFHCKBCEENWkVr0znFPHEdCTdjKyKqAYUgALNq0DtUfvyq//CaEWMk5EQW9vXabTVLWKu5KyJgmNi2xi8ctTKXsQGQu3X8+adHqg5FKiVArVH7f0d//Cn/75VTNupt0+rShPmEgFvwkTMFWF1mHO6WiBeE4anVvrCDhSAAlCgEMW1R20hiv+x7/kEav/rTzfiB9crxWuCWmuqfMRcoBaRnaAAFMiAAkFQW4jimX9/n9N25+5nzMpWArU2Sq7aXVVv/SJzNRFznQ0EW0RpsyLPpWm8REMBKAAFaqlAKNSuW7/h8//5X0Xbk9MzhrNsbzSxgeDGwXVjg0s7a4qYK1YzE3OXxzY3F7lZqez0NVok2M5DBlZFriHSUAAKNJcCoVBbiOLu3l0X/vPj997/j7Z1DzWXKKgtFIACUKC2CgREbW0rCmtQAApAgeZVAKit5XZM844D1BwKQIGgCgC1QC0UgAJQILgCQG1wiYMulTAOBaBAUygA1AK1UAAKQIHgCgC1wSVuiiUXlYQCUCCoAkAtUAsFoAAUCK4AUBtc4qBLJYxDASjQFAoAtUAtFIACUCC4AkBtUuJvtD34/R/sm3zz7e//YN832h5sigUTlYQCUCDjCgC1SdR+53vPnnh7eufuZ996593vfO/ZjPcfqgcFoEBTKADUxh0Pb9x/4NDLB4fU58zZDyYmT7x8cOjY8akzZz8w1/cfOPTA+g1N0amoJBSAAllTINOo3TpxZ3BorGLJOi8Pzt7cmvosrpbWtu1dPb/+6Hfbu3pKfdTdlta2iiuTuhqhLL9w5fD1ha6aV6OWZo/2fXp3/Prd8eu3+15IvmSEkqV6QY4sjF+/u/dI81S4+ibDQhUKZAK1T3Xu+OX8b7p7dyXmVX1Qqwqd+/mvWlrbExVQf7a0ts9+eMF7q74XBZIqntu1ZCLDSu3Nzu1tKGofOXV7/PxcBR1aFWrN6pLs0K7zzpIj/1TrkMk5t1csS+xTUbWrgEUF4qCUIgUygdpjx6fUEbETk1Mq/pjq1Hqi9uSpmQ3f3FyI4rZ1D3Xu2FmI4qd37GyTkSM2bNo89TMdraehow2oZawvGspVdk3FqF17BQQrD586WlxhWYcF/5JzZGH80yuPFBXadd5vqtg4rjRWgUygdvLNk3Qa98rVa3/c3tWjRNk6cWeg/9zA7J0R8bF7Alsn1BXnYkHsGzjXO4ZWRk5f7tCjc6z3dLntiNGjx1S5T2zbPjF5ohDFxyZPbPl2ZyGKO7t7R1+fSN9PXefv7j1iXA/78m49FDtnTLa7485rvvV61AQTk5A7Mqs6gMLrNI4P1YFd1FP9hSuHP73SpzypI/IRWzcf15gFVmHbCmuW15BhgjWEaqU7qMirZWUZMPm0ndt7faFP6kOyk2Vmgd4GbOZxwzuWTYlsivN1um2ss+PBjThupszPrxxZ8LvPwsJCV1Skg9THvxLoR3w9VQRlX1vwYP0UyARqf/rGWwa1S8srf/37Fz/c90ohiiVSV3o7hRzGwxUXJ86pcWNhKjirc0rmKi6fG/Bc9Iv70uBrzzzXX4jiYtT2Pf/iS6+8mn6kSqTq2W6cjkdO3TYTmKeNWXZRcpZPTj1tUnu1YgbSvqeZjfyimM/yhVRcFG6RrrPJ7J2o3ILNebTvUyorsmnT8EIU2zSnDE+L4lzE8LJMbaUpAzgyq1kmIX677wVjR1CVNqlNWmZWy4ltghgSfpZ5dbDdYRou2kg0TwywJGplta/QJoCxYDrX1F/ZkY+LVdO0xdonBewVM5yQyJoCmUAt92oVc3/7+z9wvArV+m9Kwo71niakRrGhqmAu8ddcLERxx9CK+mLNJEp1QN/zL+7df8CL2n2Drz27Z6DUg8XXfROAeanK2dQkda+riy4CmH0zG/nU4hb0bHSoYay5aNOVpLuJP1mhtiy/WWHBuM8ioYkj/WX5wmswJ3jk+OaOB+0ixltbTu0oLug8+kGqHtmRe6msOAU1ulsEd3rcttcrArto1xVN6ut3/e6qy2u+EosmSBHYQstryCojmuPSlvqOVYnldwtFnoYrkAnUJrzapeWV4VGxk2U8WSHTmlBL2HUB7RuFnd29I2M/9qLW7C2k7K0SqDX+i50PYnoTbuxULzmFvKi11kz1rKkoLhhrXnjR3apQS00wFZAJIhErt7TrV+TVsqe4X+xouypqPW8GHGQ8Xa1Xq9uu+O4XRPeU0wStv3Fd2VKUrDzpqUevGAzmPclV3jMkkKHhCmQCtdyr/fNf/vbq0LD6zwAMtWKndaBfjCG+gWDT/TdpM1ft7TobuwMTyiMuNwQ3bvrW1NvTXtSenJ7ZsEl8Y5by48wlekr4MkUzUFzUM4q/ZpacRV7LnlpZD0jNYekNiVlNuDfpcqhV/jLzpLxm5du9f84fWZBbE1SoeklPuGakT7kNBFNbx6s1C48mJi0wBFDxFLnY/lIop7pLDqarZ5EI1lSCfWZ4WC9emuI9K/OwgqjO5tmiJccUZ8UnIyWV5NaQzooCmUDtT97Q/wPh408ub35imxnrcq9Wf9PF/oOtwK78ouwO+9ZLbezeGRGbs+cG2Hdowh2e1Zg2losTUfzA+3O/KETxffeve/TxLYUofvTxLeq/Q8x+eOHr991f/EipKyWAqCatdlvsW7Z++77dd4p/W8LcHA5oiQ/5Umz55a0GvaqrvUvCpfK5ZImsAuKurjORV9r0UMZv1tYqsaVYRBm92apFIECzxoq6UdOKa1vycfEIYYsBlFmgdY7dTewOi43mRMXigr5IAhrwiQRHrX3WbqHozGVF4J1r85MCchnTeyBOTlEcqZcVlHjHIS4aBTKB2h07d39+c+FHI2Ol/merqe5aEtbhXWVQTp85W/x7sI6HN77z7ntrKdeZlqsUDfspFSixjDVEXk7thlQAhTaTAplAbcpptqZsq/wfL26zu3fX9JmzB4eGzY9xDw4Nn3r3ve7e7/Js5dP/+skX7KsYtvXmfneEPGtQgG/aruHx6h9xup7e6Ks3W72FZ0ZnnLphmc+eAnlGrd5/MP8zIYX6La1tW5962vw8d+u2zqb8MW6KljbpzGy8V2s3JcxrfjP5Vk3a7zmodp5Rm4PuQROgABTIhwJALVwSKAAFoEBwBYDa4BLnY01GK6AAFKhGAaAWqIUCUAAKBFcAqA0ucTUrIZ6FAlAgHwoAtUAtFIACUCC4AkBtcInzsSajFVAAClSjAFCbRC0i5lYznvAsFIACXgWA2iRqETHXO1BwEQpAgWoUAGoRMTe52FQznvAsFIACXgUyjVp2iGIlOEDEXP7DXOewrkpk5EaK07U0a47Faqqfuspf6JaIvFA7nYuVx5XmVCATqEXEXO8yWHRRIKniuV1LJjKC1N5sgw/KojMYWRvLT+mqUGtWl2SHymMq7ZJDp1aKc4uo6xNnTpojj1PXvHy7cDeMAplALSLmFlHVO22AWq8stblYMWrXPiEFK72nzco6IGJubTo03ZyqX1mZQC2PwoCIuXKIWK/HxJ5yj9qzjo9/SAmv0xzhSCdbs4t6qiNiro4JZLQSCS8HSWfuVLJeYNpSZA01jYuOBndj+ZBZE5rI7937V4Lav1vUDz224WtftJqptplAbSK2GCLminAAydBSOiIAvUWWHWRiBhIFzGzkF00MWnFRkEW+qC50mcze0c8t2JyJYAS6XH7aoU1zyvC0KM5FDC/L1FZHYUgUoVkmYCRabezwwDMmLTOriAa2CUJMP8u8OuiLvOGImFt2QJaT8V55MBOo5V4tIubawIvJAerdQGD+LwWbcqhhgOKiTeOP7ib+9HocfrPCguMS6sUAEXOT3aeZolc1dZfijCFirnfI5eliJlCb8GoRMfcwQdMdal7UepwCPxPDodYJe2XqQ04fKxcRc3kgCdG5eqnjmxK0biVfa0hPTXAxGMpudJiOQCITCmQCtdyrRcRcFTrQO4vsm3gJj0mjmWKxyLdyCq0oZjXfVZDpcl6t8pdpnzeKC16z8u3eW9sCIuaKbvLt1dLi5CyKuk/NBogLCCu+vJ74s/x4wN0MKJAJ1CJirthkRMRcvRFB64GgiXbxzA41/89PxHfNJsIWQxWzgIi57huSy/EMkCj31csEahExN/fjrFYNTOvX14MdjOn1KA5wbG4FMoHaWs1Dnx1EzKW9P/IQjavYdAm+0dmQyjsDjF7hG1KTRKGImOt0TSZXvjyjFhFzsz/+Kq1h471auylBGx2ZnNiVCov8oRXIM2pDawf7UAAKQIGUCgC1zb0BlLKbkQ0KQIHGKgDUArVQAApAgeAKALXBJW7sWorSoQAUyIICQC1QCwWgABQIrgBQG1ziLKyoqAMUgAKNVQCoBWqhABSAAsEVAGqDS6zWUgTibaxPgdKhQGMVAGrrhFoE4m3sQEfpUKCxCgC1oVDb8fDG/QcOvXxwSH3OnP1gYvLEyweHjh2fOnP2A3N9/4FDD6zf0NhBgNKhABQIrUDToDYRPVf+6Pbm1gz/JrKltW17V8+vP/rd9q6eUh91t6W1LXQ3194+nb5YY8u1NGsOTW+qX9DKH/6ak8xqLG+G50vuW5oJ1JaKmMvVXytq13822jUXxYWerq8GN3GD1aXXfza6Z7FndY947ue/amlt95bV0to+++EF7606X6TjB1dvjq1YLZnIyq292Qafv1WxtlWh1qwuJryu1lYePmmXHH4WJWG96Hjy5NnkrJuA7MoVyARqS0XMtRM7ihOo5bfKpjVqX+/f/WX/+rI5KxpJaVF78tTMhm9uLkRx27qHOnfsLETx0zt2tj0oarJh0+apn52uXZUqqr+TuWIc2PABjp0atAWorXwOk+yClXSGr9Mvsn8RiNfRhESr38VMoJZHYeARcwuSsCOzd9RncGhMCNR5eVBdOX25g8al2E+YOEfyiYMTB/rj1/t3f3V8j/MZffJ18nDnBvUt45yaK18dl46wMt7TZSyonEVmd3/2WMkOGz16bHtXTyGKn9i2fWLyRCGKj02e2PLtzkIUd3b3jr4+QXUuacFmkJFjjD9CzoiMP6gPSLQRE0y28euOg8Ouy8wCbc4pi965auvg5Kfi2EX9OALxIhAvzU07eO7tK5lAbSK2mBMxlwCa9Gr7b44w1Er+0tatYDGlH3vyS4nXuUH2vi/pqQnb0/UVZTA7DAKmBsoGu489+eVxQ9W0Xu1Lg68981y/F7V9z7/40iuvVjAQ+aslhf8TQWjMix5Pm2FtwymKqK4UjMAhe1qvViCVXkJF2sCaLprQthK+CMSLQLwVDG8zYnOayARquVfLIuaeG5hd6e3UUFgFtdL/HegXmTuGVrT/G8WFx578UmzRumQkvLJxsP6zUYNR+ZQkrLvtwI3wtIMtZlNc73v+xb37D3hRu2/wtWf3DCTyl/vT4JWNRealSueUB62y7qpyP0vuWvpQa3f9ximmpJPNoNbleyLybuJPb+v8ZiWs+QHY2ou3K4eJNy6XENvYxHLittpbWxnz3LrzOo9+kKpHdux5teptQC0zdFd0DU9XG/NclH7dG6k+OerkSKD3DBoqCMTrHXINuZgJ1Ca8WoqYWxlqC/035R7CWO9pBWhBQ/PurxJ6u7aOqO3s7h0Z+7EXtWZvIW3H0/zh+f0xaAWnyNM0THQRwI0QTZKzl+dJOmjGrBdedLcq1NKy4VaDAseycv0i6AXJAZ/zEsAI65w4vipqzWuEXfN4KTxdLWp12xXf/YLoXnOaoPUv+qbLQ23SU7dFLLF21bENXGVsuH2EzB4FMoFa7tWyiLk2Vk3H0MrI7B3rq0axACvfQBBjYqz39M2tnZcHac+hEIntWonXTYtmH0Dt1ar9ATaSxEYt/RcFmxZbDbRvy9NRPDe4J833bBs3fWvq7Wkvak9Oz2zYJL4xS/vxoVZ6PeTLGFNiZuqL3NkRaQ8mZCjcstOYTXjjICMQL+84jleeLqWtemko6jjRgwn2mVKsFy+7A4F4jTLNkcgEaktFzLXfgE2cs9sC/TfNF2UiwYAriWz3HCpCrdpk0F4wAzH7EoztMKitCf2dm3vd8E4moviB9+d+UYji++5f9+jjWwpR/OjjW1rb1xWiePbDC1+/7/60nFWxwX1A5HsI5I/Y1//Dp67sJeyqsOf0Vs7nOc9/tEyVqKzbfS+wmc/eqe1rvizU59V6KOM3K1wz85Udr20RZaR/Su0yTlnCpyM3v7i2JR8Xj5DLzwDKLNDeN7ubfHvwausRgWTnqLXPJr7e9MQ8563wjJNEDUlYJ6cojoZQc/CLRGuC2mYCtWEj5rrsq3/fTJ85W/x7sI6HN77z7nv1rwxK9CrgvH03eMBwJjYBQbx64mKxAplAbXG18nSlu3fX9JmzB4eGzY9xDw4Nn3r3ve7e76Zv5tBH/2e8NiRqqwAC8ZbSc8/EbPohipzlFQBq6+E4tLS2bX3qafPz3K3bOpvyx7gNdvcC9lTjvVq7KUEbHflVuzyS8noXqA04gfM6aNAuKAAFKlUAqAVqoQAUgALBFQBqg0tc6eqH/FAACuRPAaAWqIUCUAAKBFcAqA0ucf7WZ7QICkCBShUAaoFaKAAFoEBwBYDa4BJXuvohPxSAAvlTAKitE2oRMTd/kwctggLpFQBq64RaRMxNPyiREwrkTwGgNhRqETE3f7MFLYICa1agaVCbOBocEXPX3OW1eZBOpK2NNfMj1FqaNcdiNdVPXeUvdE00oxrLa3RGou4KZAK1iJjb2BlF5wRW4uDXkoms3NqbbfBBWRVrWxVqzeriBJRT5+mMm9Pi+XGLNvRc4szJEkcb151QjZ0aNSw9E6hFxNwa9ugaTFWMA0TMTU2ctWib2rjb14KV3tNmZR0QMZet6GtUuCoLmUAtj8KAiLnu/GG9KwOx0Cna1m0RE0kfoW3PzzbZEkdKs+sys/Ai6ZRomfDOVVslJz8Vxy7qxxExFxFzG4EzO1CzV3omUJuILYaIuf4Rw18tTfAbFmIrEThLG7FxDxExNy6IVcFs3Qo3UO2KyhVIX6cDFeUL9fk5uZKpqBMqAws/IQIuqPVGZlYRDUQRtAhFJogDWzJXoQCPwiC6rMS+ra6eGSqy2lf26oXTtFFsKUgLiY0U+bjIbKvqmioXjMPkRCK9AplALfdqETG3ZOcZvLK5yrxU6ZxS/BLm6prplJhsdvLLF8w5t1y762dmo5PNAIWzXm4CCseW7mps0Z9uEboCfrPiEcfd1sSxK4fBHCLmCiXlSCBu0lBBxFzvkGvIxUygNuHVImKufyjQ/OF3/V6P4BT5NRZzFaHWgtgU52diONTSsmEqIBPk9LFy/SLoBcltNXuKB18gT1a2WufRD1KryY5rgepGd0WhPF2tV6vtq4PD/YLonnKaoDvduK5s0UrG8SQ9tVxiiV1lE4mt9NR8z2jBrYQCmUAt92oRMTfRQ/ZPH2oFCIrfAcXM1A4Od3ZEOjnTCC5lp7GugzWr5rAsIol1iXjiu8+rVf4y+V8qPKWuLTMrgFVizh9ZOHzqaNd5WkvUSzq118rlRa23tsYZF48k37iTqBUWil/qOV55GhFzQWGtQCZQi4i5RYDwDVAfaunNUbst5I/Y139EzKXvDAnNykOUuxNmG5Tvw5CGmphJ1Oq1gfxEvURxvPK0ZreqA1k2F9l6Y11F7mbafkx8vYmIuammjFXVN6HqezcTqEXE3KYbNzmrsPP2Xd8ZWKRkgtSNZ0RRDVGltSiQCdTmuy8RMZf8SvIE3e+7Gn6Xb9o2pDLO+KddmobUJFEoIuY6XVPdGgzUrmWBqrQDEDG3UsXqnL/xXq3d1qCNjuomdp0FRHGrKgDU1gO1q3YDMkABKJBvBYBaoBYKQAEoEFwBoDa4xPleq9E6KAAF0igA1AK1UAAKQIHgCgC1wSVOs+IhDxSAAvlWAKgFaqEAFIACwRUAaoNLnO+1Gq2DAlAgjQJALVALBaAAFAiuAFAbXOI0Kx7yQAEokG8FgFqgFgpAASgQXAGgNrjE+V6r0TooAAXSKBAWtWlC4aapJfJAASgABZpagbCoTRMKt6nlQ+WhABSAAmkUCItaHl7BhMIdmF+8MX/x0vLK0q2L4/OLS8srN+aHZV2HZ2+tqNhiS7cuDsiTjQbmF5euzqiW8HRh+prOaR+PC9GMMCs/ZDMuRMVmRbZL07R1cOjijeVr4+ogJZHWFkyG8asrl6aNZcqJg5egABSAAqkV+H8K6eoEM9WYXQAAAABJRU5ErkJggg==" width="462" height="142" class="img_ev3q"></p>
<p>同时还有一点需要注意：<code>onClick</code>、<code>onChange</code> 等并不是直接挂在目标节点上的原生监听，而是 React 的<strong>合成事件</strong>（底层仍来自原生事件）。</p>
<p>常见对应关系（简化）：</p>
<table><thead><tr><th>React 合成事件</th><th>主要来源的原生事件（因控件类型略有差异）</th></tr></thead><tbody><tr><td><code>onClick</code></td><td><code>click</code></td></tr><tr><td><code>onChange</code></td><td>文本类多为 <code>input</code>/<code>change</code>；部分控件行为与原生 <code>change</code> 不完全一致</td></tr><tr><td><code>onBlur</code></td><td><code>blur</code> / <code>focusout</code></td></tr><tr><td><code>onKeyDown</code></td><td><code>keydown</code></td></tr><tr><td><code>onKeyUp</code></td><td><code>keyup</code></td></tr></tbody></table>
<p>注意：<strong><code>onBlur</code>、<code>onKeyDown</code>、<code>onKeyUp</code> 是独立的合成事件</strong>，不会被“合成进”<code>onChange</code>。<code>onChange</code> 主要解决表单控件在不同浏览器下取值变化时机不一致的问题。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="react额外处理的原因">React额外处理的原因<a href="https://docs.asmu1n.com/blog/2025/02/06/React-Event#react%E9%A2%9D%E5%A4%96%E5%A4%84%E7%90%86%E7%9A%84%E5%8E%9F%E5%9B%A0" class="hash-link" aria-label="React额外处理的原因的直接链接" title="React额外处理的原因的直接链接" translate="no">​</a></h2>
<p>在讲述<code>React</code>如何处理事件系统前,我们还是需要了解一下为什么<code>React</code>要做这样的事件。</p>
<ol>
<li class="">
<p>抹平不同浏览器之间的差异，不同浏览器之间的事件机制并不相同。而<code>React</code>自己根据<code>W3C</code>规范去实现一套适合<code>React</code>机制的事件系统。可以大大减少开发者在浏览器差异上栽跟头。</p>
</li>
<li class="">
<p>将所有事件委托至根节点<code>Root</code>或者<code>document</code>上，可以减少事件监听的数量，优化内存开销。</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="事件注册">事件注册<a href="https://docs.asmu1n.com/blog/2025/02/06/React-Event#%E4%BA%8B%E4%BB%B6%E6%B3%A8%E5%86%8C" class="hash-link" aria-label="事件注册的直接链接" title="事件注册的直接链接" translate="no">​</a></h2>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">&lt;div onClick={()=&gt;{console.log('测试')}}&gt;测试</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>这个<code>div</code>节点最终要对应一个<code>fiber</code>节点，<code>onClick</code>则作为它的<code>prop</code>。当这个<code>fiber</code>节点进入<code>render</code>阶段的<code>complete</code>阶段时，名称为<code>onClick</code>的<code>prop</code>会被识别为事件进行处理。</p>
<p>当绑定事件到root上时,root上的事件监听函数是<code>listener</code>,<code>listener</code>并不是我们所写的事件处理函数,而是一个<code>事件监听包装器</code>,用于分化<code>事件处理优先级</code>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="事件触发-事件监听器listener">事件触发 (事件监听器listener)<a href="https://docs.asmu1n.com/blog/2025/02/06/React-Event#%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91-%E4%BA%8B%E4%BB%B6%E7%9B%91%E5%90%AC%E5%99%A8listener" class="hash-link" aria-label="事件触发 (事件监听器listener)的直接链接" title="事件触发 (事件监听器listener)的直接链接" translate="no">​</a></h2>
<p><code>listener</code>做的事情可以用一句话概括,负责以不同的优先级权重来触发真正的事件流程，并传递事件执行阶段标志（<code>eventSystemFlags</code>）---区分<code>冒泡</code>和<code>捕获</code>执行。</p>
<p>比如一个元素绑定了<code>onClick</code>事件，那么点击它的时候，绑定在<code>root</code>上的<code>listener</code>会被触发，会最终使得组件中的事件被执行。</p>
<p>也就是说绑定到<code>root</code>上的事件监听<code>listener</code>只是相当于一个传令官，它按照事件的优先级去安排接下来的工作：<strong>事件对象的合成、将节点路径上的事件处理函数收集到执行路径、 事件执行，</strong> 这样在后面的调度过程中，<code>scheduler</code>才能获知当前任务的优先级，然后展开调度。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="创建合成事件对象">创建合成事件对象<a href="https://docs.asmu1n.com/blog/2025/02/06/React-Event#%E5%88%9B%E5%BB%BA%E5%90%88%E6%88%90%E4%BA%8B%E4%BB%B6%E5%AF%B9%E8%B1%A1" class="hash-link" aria-label="创建合成事件对象的直接链接" title="创建合成事件对象的直接链接" translate="no">​</a></h3>
<p><code>React</code>内部遵照<code>W3C</code>规范去实现一套事件系统，所以它的事件对象和原生事件对象并不相同。而原生的事件对象只不过是它的一个属性（<code>nativeEvent</code>）。</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 构造合成事件对象</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> event </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">SyntheticEvent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    reactName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    nativeEvent</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    nativeEventTarget</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token maybe-class-name">EventInterface</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="收集节点路径上的同类事件处理函数到执行数组中">收集节点路径上的同类事件处理函数到执行数组中<a href="https://docs.asmu1n.com/blog/2025/02/06/React-Event#%E6%94%B6%E9%9B%86%E8%8A%82%E7%82%B9%E8%B7%AF%E5%BE%84%E4%B8%8A%E7%9A%84%E5%90%8C%E7%B1%BB%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86%E5%87%BD%E6%95%B0%E5%88%B0%E6%89%A7%E8%A1%8C%E6%95%B0%E7%BB%84%E4%B8%AD" class="hash-link" aria-label="收集节点路径上的同类事件处理函数到执行数组中的直接链接" title="收集节点路径上的同类事件处理函数到执行数组中的直接链接" translate="no">​</a></h3>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag special-attr attr-name" style="color:#00a4db">onClick</span><span class="token tag special-attr attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag special-attr attr-value value javascript language-javascript punctuation" style="color:#393A34">{</span><span class="token tag special-attr attr-value value javascript language-javascript" style="color:#e3116c">onClickParent</span><span class="token tag special-attr attr-value value javascript language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  父元素</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag special-attr attr-name" style="color:#00a4db">onClick</span><span class="token tag special-attr attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag special-attr attr-value value javascript language-javascript punctuation" style="color:#393A34">{</span><span class="token tag special-attr attr-value value javascript language-javascript" style="color:#e3116c">onClickChild</span><span class="token tag special-attr attr-value value javascript language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     子元素</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">listeners: [ onClickChild, onClickParent ]</span><br></div></code></pre></div></div>
<p><code>listeners</code>中就是节点路径上收集到的同类事件处理函数,据事件的冒泡和捕获阶段来决定事件执行的顺序。</p>
<p>如果是冒泡就是从左往右执行，捕获则是从右往左执行。
<strong><code>listeners</code>中的事件处理函数排列顺序不会受冒泡和捕获的影响,冒泡和捕获是通过执行顺序的不同而实现的</strong></p>
<p><a href="https://segmentfault.com/a/1190000039108951" target="_blank" rel="noopener noreferrer" class="">本文参考文章:《深入React合成事件机制原理》</a></p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="React" term="React"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[React 组件封装思路]]></title>
        <id>https://docs.asmu1n.com/blog/2025/01/03/React-Components-Design</id>
        <link href="https://docs.asmu1n.com/blog/2025/01/03/React-Components-Design"/>
        <updated>2025-01-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[React 组件封装以及 Hook 的综合运用]]></summary>
        <content type="html"><![CDATA[<p><code>React</code> 组件封装以及 <code>Hook</code> 的综合运用</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://docs.asmu1n.com/blog/2025/01/03/React-Components-Design#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>React 用 JSX 描述 UI。通过组件封装可以提高复用与可维护性。常见手段包括：<strong>Props / ref</strong>、<strong>Context</strong>、<strong>复合组件（Compound Components）</strong>、以及后文会对比的 <strong>HOC / Hook</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="基于-props-和-ref">基于 Props 和 ref<a href="https://docs.asmu1n.com/blog/2025/01/03/React-Components-Design#%E5%9F%BA%E4%BA%8E-props-%E5%92%8C-ref" class="hash-link" aria-label="基于 Props 和 ref的直接链接" title="基于 Props 和 ref的直接链接" translate="no">​</a></h2>
<p>父组件通过 Props 控制子组件；需要命令式 API 时，用 <code>ref</code> + <code>useImperativeHandle</code> 暴露有限方法。</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 父组件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useState</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> useRef </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token maybe-class-name">ChildComponent</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">ChildHandle</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./ChildComponent'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">ParentComponent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">count</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setCount</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> childRef </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">useRef</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">ChildHandle</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">letChildSay</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        childRef</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">current</span><span class="token operator" style="color:#393A34">?.</span><span class="token method function property-access" style="color:#d73a49">childSay</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">letChildSay</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">childSay</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">ChildComponent</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">ref</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">childRef</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">count</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">count</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 子组件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> forwardRef</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> useImperativeHandle </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">ChildHandle</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">childSay</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">ChildComponent</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">forwardRef</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">ChildHandle</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">,</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name punctuation" style="color:#393A34">{</span><span class="token generic-function generic class-name"> count</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">number</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name punctuation" style="color:#393A34">}</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">ChildComponent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> count </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ref</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">useImperativeHandle</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ref</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">childSay</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'child say'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">count</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token maybe-class-name">ChildComponent</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>局限：Props / ref <strong>层层传递</strong>时，动态嵌套会很难维护，于是需要 Context 或复合组件。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="基于-context--复合组件">基于 Context + 复合组件<a href="https://docs.asmu1n.com/blog/2025/01/03/React-Components-Design#%E5%9F%BA%E4%BA%8E-context--%E5%A4%8D%E5%90%88%E7%BB%84%E4%BB%B6" class="hash-link" aria-label="基于 Context + 复合组件的直接链接" title="基于 Context + 复合组件的直接链接" translate="no">​</a></h2>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> createContext</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> useContext </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">IDrawerContext</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    drawerVisible</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">drawerToggle</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">drawerClose</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">drawerOpen</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> DrawerContext </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">createContext</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">IDrawerContext </span><span class="token generic-function generic class-name operator" style="color:#393A34">|</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name keyword" style="color:#00009f">null</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useDrawerContext</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> context </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useContext</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">DrawerContext</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">context</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'useDrawerContext must be used within a DrawerProvider'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> context</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> DrawerContext</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> useDrawerContext </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><code>useDrawerContext</code> 既做运行时约束，也消掉 TS 上的 <code>null</code>。</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">useToggle</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/Hooks/state/useToggle'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useDrawerContext</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">DrawerContext</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/service/context/Drawer'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">Drawer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> children </span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> children</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ReactNode</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">drawerVisible</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> toggle</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> drawerToggle</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setDefault</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> drawerClose</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setReverse</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> drawerOpen </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token generic-function function" style="color:#d73a49">useToggle</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name builtin">boolean</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">,</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">boolean</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">DrawerContext.Provider</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">            </span><span class="token tag attr-name" style="color:#00a4db">value</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">                drawerVisible</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">                drawerToggle</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">                drawerClose</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">                drawerOpen</span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">            </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">drawer drawer-end</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">                </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">checkbox</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">drawer-toggle</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">checked</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">drawerVisible</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onChange</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">drawerToggle</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">                </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">children</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">DrawerContext.Provider</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token maybe-class-name">Drawer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method-variable function-variable method function property-access maybe-class-name" style="color:#d73a49">PageContent</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">DrawerPageContent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> children </span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> children</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ReactNode</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">drawer-content</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">children</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token maybe-class-name">Drawer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method-variable function-variable method function property-access maybe-class-name" style="color:#d73a49">Content</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">DrawerContent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> children </span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> children</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ReactNode</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> drawerToggle </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useDrawerContext</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">drawer-side</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">label</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">drawerToggle</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">drawer-overlay</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">label</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">menu min-h-full w-[60%] bg-nav text-main md:w-80 md:p-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">children</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token maybe-class-name">Drawer</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>这是典型的<strong>复合组件</strong>：<code>Drawer</code> 提供 Provider；<code>PageContent</code> / <code>Content</code> 只负责槽位，后代可通过 <code>useDrawerContext</code> 开关抽屉，无需层层传 props。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="样例">样例<a href="https://docs.asmu1n.com/blog/2025/01/03/React-Components-Design#%E6%A0%B7%E4%BE%8B" class="hash-link" aria-label="样例的直接链接" title="样例的直接链接" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">MessageManager</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Drawer</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Drawer.PageContent</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Message</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">h-screen bg-back p-2</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">                </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 主内容：Sidebar / Navbar / Outlet ... */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Drawer.PageContent</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Drawer.Content</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">isLogin </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">UserInfo</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Drawer.Content</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Drawer</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="怎么选封装方式">怎么选封装方式<a href="https://docs.asmu1n.com/blog/2025/01/03/React-Components-Design#%E6%80%8E%E4%B9%88%E9%80%89%E5%B0%81%E8%A3%85%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="怎么选封装方式的直接链接" title="怎么选封装方式的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>方式</th><th>适合</th><th>注意</th></tr></thead><tbody><tr><td>纯 Props</td><td>结构浅、依赖明确</td><td>深层树会 prop drilling</td></tr><tr><td>ref 命令式 API</td><td>聚焦、打开弹层、播放器控制</td><td>暴露面要小</td></tr><tr><td>Context</td><td>跨层共享状态/动作</td><td>更新粒度，避免无脑大 Context</td></tr><tr><td>复合组件</td><td>有固定槽位的 UI 套件（Tabs、Drawer）</td><td>子组件应在 Provider 内使用</td></tr><tr><td>自定义 Hook</td><td>只复用逻辑、不包一层 UI</td><td>不能单独改渲染结构</td></tr><tr><td>HOC</td><td>横切增强（权限、埋点、可见性）</td><td>类型与调试成本；现代更常优先 Hook</td></tr></tbody></table>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">flowchart TD</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Need[需要复用什么?]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Need --&gt;|只要逻辑| Hook[自定义 Hook]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Need --&gt;|固定 UI 结构+槽位| CC[复合组件 + Context]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Need --&gt;|横切增强且不改子树结构| HOC[HOC]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Need --&gt;|父子直接协作| Props[Props / 少量 ref]</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://docs.asmu1n.com/blog/2025/01/03/React-Components-Design#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<ul>
<li class="">组件名用 <strong>PascalCase</strong>；<code>children</code> 类型是 <code>React.ReactNode</code>（不是 <code>JSX.Elemnt</code>）</li>
<li class="">深层共享优先 <strong>Context / 复合组件</strong>，而不是无限透传</li>
<li class="">逻辑复用优先 <strong>Hook</strong>；需要包一层渲染策略再考虑 HOC（另见 <a class="" href="https://docs.asmu1n.com/blog/2025/02/27/React-HOC">React HOC</a>）</li>
</ul>
<p>一句话：<strong>Props 管配置，Context 管跨层协作，复合组件管槽位，Hook 管逻辑。</strong></p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="React" term="React"/>
        <category label="TypeScript" term="TypeScript"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Web Worker]]></title>
        <id>https://docs.asmu1n.com/blog/2024/12/26/WebWorker</id>
        <link href="https://docs.asmu1n.com/blog/2024/12/26/WebWorker"/>
        <updated>2024-12-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Web Worker 独立于页面的主线程，可以并行执行任务，不会阻塞主线程。]]></summary>
        <content type="html"><![CDATA[<p><code>Web Worker</code> 独立于页面的主线程，可以并行执行任务，不会阻塞主线程。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://docs.asmu1n.com/blog/2024/12/26/WebWorker#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>首先需要明确的是<code>JS</code>是一个单线程语言, 这就意味着<code>JS</code>在执行的时候只能执行一个任务, 当执行一个任务的时候, 其他任务都需要等待。
当然，有些操作是可以异步执行的，比如<code>setTimeout</code>、<code>ajax</code>、<code>Promise</code>等。这些操作往往都存在一个<code>挂起</code>状态,比如在等待网络请求响应的过程,主线程并不需要停滞等待, 而是可以去执行其他任务。等到网络请求响应后，会将任务推入任务队列中等待主线程去执行。
虽然<code>JS</code>只支持单线程，但这并不意味着浏览器是单线程执行任务的。相反浏览器本身是多线程的，比如<code>GUI渲染线程</code>、<code>JS引擎线程</code>、<code>事件触发线程</code>、<code>定时器触发线程</code>、<code>异步http请求线程</code>等。
如果主线程执行一个计算密集型任务，会导致其他任务无法执行。为了解决一些计算密集型任务在<code>JS</code>中会导致主线程阻塞，无法即时响应用户的交互，让网页看起来像是卡死的情况，<code>HTML5</code>中引入了<code>Web Worker</code>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="web-worker">Web Worker<a href="https://docs.asmu1n.com/blog/2024/12/26/WebWorker#web-worker" class="hash-link" aria-label="Web Worker的直接链接" title="Web Worker的直接链接" translate="no">​</a></h2>
<p><code>Web Worker</code>是一个独立于主线程的线程，我们可以将一些计算密集型任务交给<code>Worker</code>线程去进行处理，这样并不会导致主线程阻塞从而提高用户的体验。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="注意点">注意点<a href="https://docs.asmu1n.com/blog/2024/12/26/WebWorker#%E6%B3%A8%E6%84%8F%E7%82%B9" class="hash-link" aria-label="注意点的直接链接" title="注意点的直接链接" translate="no">​</a></h3>
<ol>
<li class="">通过 URL 引入脚本实例化 <code>Worker</code>。现代打包工具推荐：<code>new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })</code>。</li>
<li class=""><code>Worker</code> 线程本身无法直接读取本地磁盘文件，只能通过网络请求或主线程传入的数据获取资源。</li>
<li class="">引入脚本通常受同源策略限制。</li>
<li class=""><code>Worker</code> 与主线程不处于同一上下文，<strong>无法直接访问主线程 DOM</strong>。</li>
<li class="">与主线程通信的主通道是 <code>postMessage</code>（还可配合 <code>MessageChannel</code>、Transferable 对象等）。仅支持可被<strong>结构化克隆</strong>的数据类型；<code>Function</code>、部分 <code>Error</code> 等无法直接传输。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="示例">示例<a href="https://docs.asmu1n.com/blog/2024/12/26/WebWorker#%E7%A4%BA%E4%BE%8B" class="hash-link" aria-label="示例的直接链接" title="示例的直接链接" translate="no">​</a></h2>
<p>我们假设有一个计算密集型任务函数，直接在主线程中执行会导致我们的页面阻塞几秒，为了优化这一点，我们将这部分任务交给<code>Worker</code>线程去执行。</p>
<div class="language-TS language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">routePositionList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> temp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Date</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">while</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">Date</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> temp </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">//</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">item </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> item </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>假设这个就是我们的计算密集型任务函数。(通过<code>While</code>空循环模拟耗时)</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="思路">思路<a href="https://docs.asmu1n.com/blog/2024/12/26/WebWorker#%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="思路的直接链接" title="思路的直接链接" translate="no">​</a></h3>
<p>我们将这个任务交给<code>Worker</code>线程去进行处理，通常这需要一定的时间。由于任务在<code>Worker</code>线程进行处理，主线程因此处于空闲状态等待<code>Worker</code>线程通过<code>postMessage</code>事件通知主线程处理结果。
这个过程跟我们的<code>ajax</code>请求是类似的。我们可以通过<code>Promise</code>封装这个过程。通过<code>Promise</code>链去优化代码。</p>
<div class="language-TS language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 主线程代码</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//* 基于Web Worker通过postMessage事件回调通信的原理,包装在Promise里,按照异步操作思维响应并处理数据</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> worker </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Worker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./worker.js'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">runTaskInWorker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">worker</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Worker</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name builtin">Promise</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">resolve</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> reject</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">onMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> MessageEvent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            worker</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">removeEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'message'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> onMessage</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> success</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> result </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">success</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token function" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        worker</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">addEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'message'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> onMessage</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        worker</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">test</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> testData </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">Array</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">from</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> length</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">10000</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> i</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> i</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">runTaskInWorker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">worker</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">testData</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Success'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'失败了'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>这段代码中，<code>runTaskInWorker</code>去负责与<code>Worker</code>线程进行通信，根据<code>Worker</code>返回的结果判断任务是否成功完成并返回结果。<code>test</code>函数调用<code>runTaskInWorker</code>函数传入处理的数据并显示处理结果。(需要注意的是，在<code>runTaskInWorker</code>中要及时移除<code>message</code>事件回调，防止内存泄漏。)</p>
<div class="language-TS language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// worker.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">routePositionList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> temp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Date</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">while</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">Date</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> temp </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">//</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">item </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> item </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//* 处理事件，根据处理结果向主线程通信</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">workerMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> data </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">routePositionList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> success</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> result</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> error</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">message </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'处理失败'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> success</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">addEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'message'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> workerMessage</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<p><code>Worker</code>线程这边对传入的数据进行处理并根据结果返回相应的数据以告知主线程任务完成情况。</p>
<div class="language-TSX language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">//业务代码</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">loading</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setLoading</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">onTest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setLoading</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">test</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setLoading</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">/*</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    ....</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    ....</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    .... </span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-center</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">onTest</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">m-5 h-10 w-20 rounded-md bg-slate-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">disabled</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">loading</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">                    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">loading </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'执行中'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'点击测试'</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<p>通过<code>Promise</code>链，我们可以更加优雅的方式处理<code>Web Worker</code>的通信，在等待处理的过程中显示处理状态。</p>
<p><strong>这样即使进行一些计算密集型任务，也不用担心处理过程中主线程阻塞导致页面卡死。</strong></p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="性能优化" term="性能优化"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Axios封装(第二版)]]></title>
        <id>https://docs.asmu1n.com/blog/2024/12/17/Request</id>
        <link href="https://docs.asmu1n.com/blog/2024/12/17/Request"/>
        <updated>2024-12-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Axios封装]]></summary>
        <content type="html"><![CDATA[<p><code>Axios</code>封装</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://docs.asmu1n.com/blog/2024/12/17/Request#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>本文基于<code>Axios</code>进行二次封装,旨在提供一种符合直觉且能够在大部分场景上进行使用的封装方式。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第一步创建axios实例配置统一的拦截器">第一步创建Axios实例,配置统一的拦截器<a href="https://docs.asmu1n.com/blog/2024/12/17/Request#%E7%AC%AC%E4%B8%80%E6%AD%A5%E5%88%9B%E5%BB%BAaxios%E5%AE%9E%E4%BE%8B%E9%85%8D%E7%BD%AE%E7%BB%9F%E4%B8%80%E7%9A%84%E6%8B%A6%E6%88%AA%E5%99%A8" class="hash-link" aria-label="第一步创建Axios实例,配置统一的拦截器的直接链接" title="第一步创建Axios实例,配置统一的拦截器的直接链接" translate="no">​</a></h2>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> axios</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AxiosRequestConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> AxiosResponse</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> InternalAxiosRequestConfig </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'axios'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> showMessage </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/component/MessageManager'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">IResponse</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name"> </span><span class="token class-name operator" style="color:#393A34">=</span><span class="token class-name"> </span><span class="token class-name builtin">any</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    success</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    data</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    token</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">IQueryList</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    itemList</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 列表项数组；若 T 本身已是数组类型，可再按业务调整</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">IRequestConfig</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">AxiosRequestConfig</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    toastError</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">IResponseParams</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name"> </span><span class="token class-name operator" style="color:#393A34">=</span><span class="token class-name"> </span><span class="token class-name builtin">any</span><span class="token class-name punctuation" style="color:#393A34">,</span><span class="token class-name"> </span><span class="token class-name constant" style="color:#36acaa">D</span><span class="token class-name"> </span><span class="token class-name operator" style="color:#393A34">=</span><span class="token class-name"> </span><span class="token class-name builtin">any</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">AxiosResponse</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name punctuation" style="color:#393A34">,</span><span class="token class-name"> </span><span class="token class-name constant" style="color:#36acaa">D</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    config</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> InternalAxiosRequestConfig </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain"> IRequestConfig</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/** 业务失败可带 toasted，避免外层重复 toast */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">RequestError</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    toasted</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> toasted </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">super</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">toasted </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> toasted</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> axiosInstance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> axios</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    baseURL</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/api'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//无需登录验证的接口</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> noAuthRequestList </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'/user/login'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/user/register'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/song/list'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">axiosInstance</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">interceptors</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> InternalAxiosRequestConfig </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain"> IRequestConfig</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">noAuthRequestList</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">url </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> token </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> localStorage</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getItem</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'token'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'请先登录'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">showMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'warning'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'请先登录'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">RequestError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'请先登录'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Authorization </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> token</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">showMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'error'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">axiosInstance</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">interceptors</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">response</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> IResponseParams</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">IResponse</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">success</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                localStorage</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setItem</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'token'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> toastError </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">toastError </span><span class="token operator" style="color:#393A34">??</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 服务端响应了数据,但是处理结果是失败的</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">toastError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token function" style="color:#d73a49">showMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'error'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// toasted: true，外层 Request 不再重复 toast</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">RequestError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> e </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">RequestError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">String</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<blockquote>
<p><code>RequestError</code> 定义见下方 <code>Request</code> 方法一节。</p>
</blockquote>
<p><em>关于拦截器的具体细节,大部分与之前保持一致<a class="" href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript">Axios 封装中的 TypeScript 实践</a></em></p>
<p>值得注意的是 <code>IRequestConfig</code> 在原有基础上拓展了 <code>toastError</code>：服务端返回了业务失败时默认弹错误提示。</p>
<p>高并发或批量请求场景下，多个 toast 会刷屏，可显式传 <code>toastError: false</code> 关闭（仅限业务失败；网络错误等仍可在外层处理）。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="封装-request-方法导出给外部使用">封装 Request 方法，导出给外部使用<a href="https://docs.asmu1n.com/blog/2024/12/17/Request#%E5%B0%81%E8%A3%85-request-%E6%96%B9%E6%B3%95%E5%AF%BC%E5%87%BA%E7%BB%99%E5%A4%96%E9%83%A8%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="封装 Request 方法，导出给外部使用的直接链接" title="封装 Request 方法，导出给外部使用的直接链接" translate="no">​</a></h2>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// RequestError 已在拦截器上方定义</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">Request</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name operator" style="color:#393A34">=</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">any</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">requestConfig</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> IRequestConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> extraConfig</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> IRequestConfig</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">IResponse</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> Response </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> axiosInstance</span><span class="token punctuation" style="color:#393A34">.</span><span class="token generic-function function" style="color:#d73a49">request</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">IResponse</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">extraConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">requestConfig </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> Response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 业务失败若已在拦截器 toast，这里不再弹；网络错误等再提示</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">RequestError</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">toasted</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> message </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> e </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">String</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">showMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'error'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> message </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> e </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>拦截器业务失败处改为：<code>return Promise.reject(new RequestError(data.message, true));</code></p>
<p><code>Request</code> 接收 <code>requestConfig</code> 与 <code>extraConfig</code>，先展开 <code>extraConfig</code> 再展开 <code>requestConfig</code>，因此 <strong><code>requestConfig</code> 优先级更高</strong>。</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> RequestConstructor </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">RD</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">baseConfig</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> IRequestConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> requestDataProcessing</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> IRequestDataProcessing</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">RD</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">R</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">requestParams</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> extraConfig</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> IRequestConfig</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 每次请求拷贝 baseConfig，禁止原地改共享 config（并发时会互相污染）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> config</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> IRequestConfig </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">baseConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">baseConfig</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">headers </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// File / FormData 等无法 structuredClone 时，按场景浅拷贝或跳过 clone</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> requestParamsCopy</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> requestParams</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            requestParamsCopy </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">structuredClone</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">requestParams</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            requestParamsCopy </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> requestParams</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">requestDataProcessing</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">beforeRequest</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> beforeRequestResult </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> requestDataProcessing</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">beforeRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">requestParamsCopy</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> extraConfig</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">beforeRequestResult</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                requestParamsCopy </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> beforeRequestResult</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">requestDataProcessing</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">afterResponse</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">transformResponse </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">requestDataProcessing</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">afterResponse</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">method </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'get'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">method </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'GET'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">method</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">Request</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">R</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> params</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> requestParamsCopy </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> extraConfig</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">Request</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">R</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> requestParamsCopy </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> extraConfig</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> RequestConstructor</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><code>RequestConstructor</code>才是我们最终默认导出的方法(<code>Request</code>方法的再次封装,设计用于常规场景,特殊情况下也可以使用具名导出的<code>Request</code>直接发送请求)。</p>
<p>让我们一步步分析<code>RequestConstructor</code>到底帮助我们完成了什么</p>
<ul>
<li class="">首先<code>RequestConstructor</code>接收两个参数,<code>config</code>和<code>requestDataProcessing</code>。</li>
<li class=""><code>config</code>的类型是<code>IRequestConfig</code>,它是我们在<code>Axios</code>实例中配置的请求配置。</li>
<li class=""><code>requestDataProcessing</code>的类型是<code>IRequestDataProcessing&lt;T, RD&gt;</code>,它是一个泛型类型,它接受两个泛型参数,分别是<code>T</code>和<code>RD</code>。</li>
<li class=""><code>T</code>是请求参数的类型,<code>RD</code>是请求数据的类型。</li>
</ul>
<ol>
<li class="">
<p>接收<code>config</code>和<code>requestDataProcessing</code>返回一个<code>Request</code>方法。</p>
</li>
<li class="">
<p><code>Request</code>方法接收两个参数,<code>requestParams</code>和<code>extraConfig</code>并返回数据请求的结果。</p>
</li>
</ol>
<p>正如<code>RequestConstructor</code>的字面意思,它作为一个构造器,根据我们传入的<code>config</code>(基本配置参数),<code>requestDataProcessing</code>(数据处理回调函数,请求前和响应后)生成了一个新的<code>Request</code>方法。</p>
<p>在业务逻辑中,调用生成的<code>Request</code>方法并传入请求所需的参数<code>requestParams</code>,这时我们还可以传入一个可选的<code>extraConfig</code>来补充<code>config</code>。在部分情况下,我们可能需要动态的调整<code>config</code>的某些属性,这时就可以传入<code>extraConfig</code>来实现。</p>
<p>举个简单的例子</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">//api.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> fetchDemo </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">RequestConstructor</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">{</span><span class="token generic-function generic class-name">name</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name builtin">string</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">}</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    url</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">'test'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    method</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">'get'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// something.tsx</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">useEffect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> controller </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">AbortController</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> request </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetchDemo</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">name</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">'something'</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">signal</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> controller</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">signal</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    controller</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">abort</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">something</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>这是在<code>React</code>项目中非常常见的使用场景。通过<code>extraConfig</code>字段,我们可以在<code>something.tsx</code>传入一个<code>controller.signal</code>,从而实现请求的取消。在这种<code>config</code>取决于业务的场景下,光依靠<code>api.ts</code>就已经定义的<code>config</code>字段是无法实现的。</p>
<p>将<code>extraConfig</code>字段在业务组件里作为一个可选参数传入,即保持常规情况下简洁的配置,又能灵活的应对特殊情况。同时也符合使用直觉,不用在定义<code>config</code>时候就考虑太多,只需要把最基本的配置定义好,比如<code>url</code>和<code>method</code>(在这一步也可以定义接收参数的类型,只需要向<code>RequestConstructor</code>传递一个类型即可,提供必要的<code>TS</code>推导支持)毕竟这部分配置项跟我们的业务组件没有什么联系。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用实例">使用实例<a href="https://docs.asmu1n.com/blog/2024/12/17/Request#%E4%BD%BF%E7%94%A8%E5%AE%9E%E4%BE%8B" class="hash-link" aria-label="使用实例的直接链接" title="使用实例的直接链接" translate="no">​</a></h2>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">//song.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//传入类型声明接收的参数数据结构</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> getSongList </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">RequestConstructor</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">GetSongParams</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    method</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'get'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation constant" style="color:#36acaa">BASEURL</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">/list</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">//传入类型声明接收的参数数据结构</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> addSong </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">RequestConstructor</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">AddSongParams</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        method</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'post'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation constant" style="color:#36acaa">BASEURL</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">/add</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string-property property" style="color:#36acaa">'Content-Type'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'multipart/form-data'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 支持直接修改params的属性</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 同时支持返回一个新的params(Tips:返回请求所需的完整数据)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">beforeRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">audio </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">audio </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> FileList</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">image </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">image </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> FileList</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token doc-comment comment" style="color:#999988;font-style:italic">/**</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">         * // 仅支持返回完整的数据,不支持直接修改</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">        afterResponse(response) </span><span class="token doc-comment comment punctuation" style="color:#393A34;font-style:italic">{</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"></span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">            return </span><span class="token doc-comment comment punctuation" style="color:#393A34;font-style:italic">{</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"></span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">                ...response</span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">            </span><span class="token doc-comment comment punctuation" style="color:#393A34;font-style:italic">}</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"></span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">        </span><span class="token doc-comment comment punctuation" style="color:#393A34;font-style:italic">}</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"></span><br></div><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">         */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// something.tsx</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> songListResponse</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// IResponse&lt;IQueryList&lt;ISong[]&gt;&gt; | undefined</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        error</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        isLoading </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useSWR</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> key</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'songList'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pageIndex</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pageSize</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">20</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> pageIndex</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pageSize </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 传入类型声明响应数据结构(getSongList&lt;IQueryList&lt;ISong[]&gt;&gt;)     </span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// getSongList=RequestConstructor&lt;GetSongParams&gt; --&gt; { pageIndex, pageSize }: GetSongParams</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token generic-function function" style="color:#d73a49">getSongList</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">IQueryList</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">ISong</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">[</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">]</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> pageIndex</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pageSize </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">onSubmit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AddSubmitProps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">addSong</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">showMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'success'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'添加成功'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                position</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'topEnd'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">unknown</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic">// 若拦截器已 toast，这里可只记日志；否则：</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> message </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> error </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> error</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'请求失败'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">showMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'error'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> message</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> position</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'topEnd'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://docs.asmu1n.com/blog/2024/12/17/Request#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">flowchart TD</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    A[业务调用 getSongList / addSong] --&gt; B[RequestConstructor 拷贝 config]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    B --&gt; C[beforeRequest 可选改参]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    C --&gt; D[Request 合并 extraConfig]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    D --&gt; E[axios 拦截器]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    E --&gt;|业务 success| F[返回 data]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    E --&gt;|业务失败| G[toast + reject Error]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    E --&gt;|网络错误| H[外层 catch 再提示]</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>共享 <code>baseConfig</code> 不要原地写</strong>，每次请求浅拷贝</li>
<li class=""><strong>reject 统一用 <code>Error</code></strong>，方便 <code>e.message</code></li>
<li class=""><strong>toast 职责单一</strong>，避免拦截器与业务 catch 各弹一次</li>
<li class="">上传场景慎用 <code>structuredClone(File/FileList)</code></li>
</ul>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="网络" term="网络"/>
        <category label="TypeScript" term="TypeScript"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Redux简单记录]]></title>
        <id>https://docs.asmu1n.com/blog/2024/11/15/Redux</id>
        <link href="https://docs.asmu1n.com/blog/2024/11/15/Redux"/>
        <updated>2024-11-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Redux使用有感]]></summary>
        <content type="html"><![CDATA[<p><code>Redux</code>使用有感</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redux简述">Redux简述<a href="https://docs.asmu1n.com/blog/2024/11/15/Redux#redux%E7%AE%80%E8%BF%B0" class="hash-link" aria-label="Redux简述的直接链接" title="Redux简述的直接链接" translate="no">​</a></h2>
<p><code>Redux</code>是一个全局状态管理工具,与<code>pinia</code>不同,它并不依托于某个框架绑定使用。当然很多基于<code>React</code>搭建的项目都使用<code>Redux</code>作为全局状态管理工具。本文涉及的示例也是基于<code>React</code>的。
<code>Redux</code>的特点：</p>
<ul>
<li class="">不可变更新,即更新数据的方式永远是重新赋一个全新的值,而不是直接修改旧值。</li>
<li class="">通过所谓的<code>action</code>函数去<code>dispatch</code>我们配置的全局<code>store</code>，<code>store</code>里的<code>reducer</code>函数去更新数据(<code>action</code>和<code>reducer</code>存在绑定关系 ---Toolkit)。</li>
<li class="">纯函数,即更新数据的函数必须是纯函数,即函数的返回值只依赖于函数的输入值,而不依赖于函数的外部状态。</li>
<li class="">单向数据流：UI <code>dispatch(action)</code> → <code>reducer</code> 计算新 state → <code>store</code> 通知订阅者 → UI 更新。</li>
</ul>
<p><em>本文主要讲述Toolkit的实现模板,这也是<code>Redux</code>官方目前所推崇的配置方式</em></p>
<p>Tips⭐⭐:
<code>Redux Toolkit</code>在<code>reducer</code>里用<code>immer</code>，让你可以用“看起来像直接改 state”的写法。但本质上 immer 通过 proxy <strong>最终仍产出新的 state 引用</strong>；真正写进 store 的是新状态，而不是在原地永久变异旧对象。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redux的构成">Redux的构成<a href="https://docs.asmu1n.com/blog/2024/11/15/Redux#redux%E7%9A%84%E6%9E%84%E6%88%90" class="hash-link" aria-label="Redux的构成的直接链接" title="Redux的构成的直接链接" translate="no">​</a></h2>
<p>通过示例去理解<code>Redux</code>的构成,帮助我们去理解<code>Redux</code>的工作原理以及所提到的名词。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="首先从根文件出发">首先从根文件出发<a href="https://docs.asmu1n.com/blog/2024/11/15/Redux#%E9%A6%96%E5%85%88%E4%BB%8E%E6%A0%B9%E6%96%87%E4%BB%B6%E5%87%BA%E5%8F%91" class="hash-link" aria-label="首先从根文件出发的直接链接" title="首先从根文件出发的直接链接" translate="no">​</a></h3>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> configureStore </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@reduxjs/toolkit'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> postsReducer </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./features/postSlice'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> usersReducer </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./features/usersSlice'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> notificationsReducer </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./features/notificationsSlice'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> TypedUseSelectorHook</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> useDispatch</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> useSelector </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react-redux'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> store </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">configureStore</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    reducer</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        posts</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> postsReducer</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        users</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> usersReducer</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        notifications</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> notificationsReducer</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> store</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">RootState</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ReturnType</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> store</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">getState</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> useAppSelector</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> TypedUseSelectorHook</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">RootState</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> useSelector</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">useAppDispatch</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">typeof</span><span class="token plain"> store</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">dispatch </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> useDispatch</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<ul>
<li class="">
<p><code>configureStore</code>帮助我们去创建了一个<code>store</code>对象,这是全局且唯一的根对象。</p>
</li>
<li class="">
<p><code>configureStore</code>接受一个对象参数配置去创建<code>store</code>对象,其中<code>reducer</code>这个属性可以传入我们创建的<code>Reducer</code>片段。或者换个理解方式,假设项目的全局状态分为三大板块,分别是<code>posts</code>、<code>user</code>、<code>notice</code>,我们最好对他们分开进行处理。这样我们就分别给它们创建一个<code>Reducer</code>片段,各自的<code>Reducer</code>片段去处理各自的状态。(<code>Reducer</code>后续会提到的,稍安勿躁)。</p>
</li>
<li class="">
<p>默认导出<code>store</code>,这个<code>store</code>对象就是我们全局唯一的根对象。后续我们所有的全局数据操作都会在这个<code>store</code>对象上进行。</p>
</li>
</ul>
<p><em>后面的还有三行代码是自定义我们的<code>Selector</code>、<code>Dispatch</code>触发器并提供类型推导,帮助我们在项目中处理全局状态时提供类型提示和约束---(JS可无视)</em></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="reducer片段"><code>Reducer</code>片段<a href="https://docs.asmu1n.com/blog/2024/11/15/Redux#reducer%E7%89%87%E6%AE%B5" class="hash-link" aria-label="reducer片段的直接链接" title="reducer片段的直接链接" translate="no">​</a></h3>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> notificationsSlice </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createSlice</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'notifications'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   initialState</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   reducers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       </span><span class="token function" style="color:#d73a49">allNotificationsRead</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">state</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">           state</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">forEach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">notification</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">               notification</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">           </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> notificationsSlice</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">reducer</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> allNotificationsRead </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> notificationsSlice</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">actions</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">selectAllNotifications</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">state</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> RootState</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> state</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">notifications</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<ul>
<li class="">
<p><code>createSlice</code>创建了一个<code>Reducer</code>片段,它接受一个对象参数,其中<code>name</code>属性是我们的<code>Reducer</code>片段的名称,<code>initialState</code>属性是我们的<code>Reducer</code>片段的初始状态。</p>
</li>
<li class="">
<p><code>reducers</code>属性则是我们的<code>Reducer</code>片段的<code>reducer</code>函数列表,它接受一个对象参数,其中的键值就是我们的<code>reducer</code>函数,键名就是我们的<code>reducer</code>函数的名称。(这里我使用<code>ES6</code>的简写形式)<strong><code>createSlice</code>会自动生成与与<code>reducer</code>同名的<code>action</code>构造函数，所以下面我们可以具名导出与之同名的<code>action</code>构造函数</strong></p>
</li>
<li class="">
<p>默认导出我们的<code>Reducer</code>片段,这个<code>Reducer</code>片段将传入我们的根<code>store</code>并通过<code>state.notifications</code>访问。</p>
</li>
<li class="">
<p>具名导出我们 <code>Reducer</code> 片段的 <code>action</code> 构造函数（<code>allNotificationsRead</code>），它们返回 <code>action</code> 对象，再通过 <code>dispatch</code> 触发更新。</p>
</li>
<li class="">
<p>同时我们导出了一个<code>selector</code>函数(<code>selectAllNotifications</code>),通过<code>useSelector</code>触发并返回我们<code>Reducer</code>片段的当前<code>state</code>(<em>当前片段<code>state</code>位于<code>store.notifications</code></em>)。</p>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用示例片段">使用示例(片段)<a href="https://docs.asmu1n.com/blog/2024/11/15/Redux#%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B%E7%89%87%E6%AE%B5" class="hash-link" aria-label="使用示例(片段)的直接链接" title="使用示例(片段)的直接链接" translate="no">​</a></h3>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> useAppSelector</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> useAppDispatch </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/store'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> selectAllUsers </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/store/features/usersSlice'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> selectAllNotifications</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> allNotificationsRead </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@/store/features/notificationsSlice'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> useLayoutEffect </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">NotificationsList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> notifications </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useAppSelector</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">selectAllNotifications</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> users </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useAppSelector</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">selectAllUsers</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> dispatch </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useAppDispatch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">useLayoutEffect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">dispatch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">allNotificationsRead</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">dispatch</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong><code>useAppSelector</code>是我自定义的<code>useSelector</code>仅提供类型推导支持--可看作<code>useSelector</code></strong>
<strong><code>useAppDispatch</code>是我自定义的<code>useDispatch</code>仅提供类型推导支持--可看作<code>useDispatch</code></strong></p>
<ul>
<li class="">
<p><code>useAppSelector</code>触发我们的<code>selector</code>函数,并返回当前<code>state</code>的数据。通过我们具名导出的<code>selector</code> (<code>selectAllNotifications</code>，<code>selectAllUsers</code>)函数去获取我们的<code>notifications</code>和<code>user</code>的当前<code>state</code>。</p>
</li>
<li class="">
<p><code>useAppDispatch</code>返回<code>store.dispatch</code>,并传入具名导出的<code>action</code>构造函数(<code>allNotificationsRead</code>)所返回的<code>action</code>对象去触发<code>Reducer</code></p>
</li>
</ul>
<p>...关于涉及异步操作的部分，后续再补充。</p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="TypeScript" term="TypeScript"/>
        <category label="Redux" term="Redux"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[TailwindCSS简单记录]]></title>
        <id>https://docs.asmu1n.com/blog/2024/11/15/TailwindCSS</id>
        <link href="https://docs.asmu1n.com/blog/2024/11/15/TailwindCSS"/>
        <updated>2024-11-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Tailwindcss 使用有感]]></summary>
        <content type="html"><![CDATA[<p><code>Tailwindcss</code> 使用有感</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tailwindcss-简述">TailwindCSS 简述<a href="https://docs.asmu1n.com/blog/2024/11/15/TailwindCSS#tailwindcss-%E7%AE%80%E8%BF%B0" class="hash-link" aria-label="TailwindCSS 简述的直接链接" title="TailwindCSS 简述的直接链接" translate="no">​</a></h2>
<p>之前的文章也提到过这个 CSS 方案：<a class="" href="https://docs.asmu1n.com/blog/2024/10/24/CSS_scheme">CSS 样式隔离方案</a>。最近一直在用，简单记一下体验。</p>
<p>多数情况下 Tailwind 以 <strong>PostCSS 插件</strong> 形式接入。官方更推荐工具链围绕 Tailwind 本身，不必再叠一层 Sass/Less；混用可以，但收益有限，配置也更绕。</p>
<p>最大特点：不必先为每个元素起类名再写一套 CSS，而是用大量<strong>原子类</strong>像搭积木一样拼样式。同时可以通过 <code>theme.extend</code> 扩展色板、间距等设计 token。</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">/** </span><span class="token doc-comment comment keyword" style="color:#00009f;font-style:italic">@type</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">{</span><span class="token doc-comment comment class-name keyword" style="color:#00009f;font-style:italic">import</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">(</span><span class="token doc-comment comment class-name string" style="color:#e3116c;font-style:italic">'tailwindcss'</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">)</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">.</span><span class="token doc-comment comment class-name" style="color:#999988;font-style:italic">Config</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">}</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">content</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'./index.html'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./src/**/*.{js,ts,jsx,tsx}'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">theme</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">extend</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">colors</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">lightBlue</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'#04A5FF'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">lightGray</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'#6e6e6e'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="存在的问题及思路">存在的问题及思路<a href="https://docs.asmu1n.com/blog/2024/11/15/TailwindCSS#%E5%AD%98%E5%9C%A8%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="存在的问题及思路的直接链接" title="存在的问题及思路的直接链接" translate="no">​</a></h2>
<p>原子类带来的副作用是：模板上类名很长，可读性/维护性压力变大，也像在写「内联样式」，复用要另想办法。</p>
<p>官方路径主要是：</p>
<ol>
<li class=""><strong>组件化</strong>：React / Vue 等把重复 UI 抽成组件，样式跟着组件走</li>
<li class=""><strong><code>@layer components</code> + <code>@apply</code></strong>：沉淀真正共享的模式类</li>
</ol>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@tailwind</span><span class="token atrule" style="color:#00a4db"> base</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@tailwind</span><span class="token atrule" style="color:#00a4db"> components</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@tailwind</span><span class="token atrule" style="color:#00a4db"> utilities</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@layer</span><span class="token atrule" style="color:#00a4db"> base</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector" style="color:#00009f">*</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">word-break</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> break-all</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@layer</span><span class="token atrule" style="color:#00a4db"> components</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* 适合跨页面复用的「模式」类，而不是某个页面私有的一坨样式 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector class" style="color:#00009f">.card-panel</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> rounded-lg bg-[</span><span class="token atrule hexcode color" style="color:#00a4db">#f4f7f7</span><span class="token atrule" style="color:#00a4db">] p-4 shadow-sm</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector class" style="color:#00009f">.muted-text</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> text-xs text-[</span><span class="token atrule hexcode color" style="color:#00a4db">#6e6e6e</span><span class="token atrule" style="color:#00a4db">]</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector class" style="color:#00009f">.class-bg</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">background-image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token url function" style="color:#d73a49">url</span><span class="token url punctuation" style="color:#393A34">(</span><span class="token url string url" style="color:#36acaa">'/src/assets/img/pattern.png'</span><span class="token url punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">linear-gradient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token unit">deg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token hexcode color">#f9fdff</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token hexcode color">#d9edff</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">background-position</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> top right</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">background-repeat</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> no-repeat</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>注意：</strong> <code>@layer</code> 里声明的类仍是<strong>全局</strong>的。</p>
<ul>
<li class="">适合：按钮、卡片、表单行等<strong>可复用模式</strong></li>
<li class="">不适合：把某一页私有布局全塞进全局 CSS</li>
</ul>
<p>页面私有样式优先放在组件的 <code>className</code> 组合里，或 CSS Modules，而不是无限往全局 layer 堆。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="怎么和设计协作">怎么和设计协作<a href="https://docs.asmu1n.com/blog/2024/11/15/TailwindCSS#%E6%80%8E%E4%B9%88%E5%92%8C%E8%AE%BE%E8%AE%A1%E5%8D%8F%E4%BD%9C" class="hash-link" aria-label="怎么和设计协作的直接链接" title="怎么和设计协作的直接链接" translate="no">​</a></h2>
<p>把 Tailwind 当<strong>设计 token 的执行器</strong>比当「CSS 替代品」更准确。</p>
<p>若团队（尤其设计）维护一份清晰的色板 / 字号 / 间距表，并写进 <code>theme.extend</code>，开发会顺很多——积木已经分类好，按说明书搭即可。</p>
<table><thead><tr><th>做法</th><th>适合</th></tr></thead><tbody><tr><td>纯原子类堆在 JSX</td><td>快速原型、局部样式</td></tr><tr><td>组件抽离 + 原子类</td><td>中大型业务 UI</td></tr><tr><td><code>@apply</code> 组件类</td><td>少数稳定模式，避免滥用</td></tr><tr><td>主题色运行时切换</td><td>配合 CSS 变量（见<a class="" href="https://docs.asmu1n.com/blog/2025/09/19/customTheme">自定义主题色</a> 随记）</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="个人感想">个人感想<a href="https://docs.asmu1n.com/blog/2024/11/15/TailwindCSS#%E4%B8%AA%E4%BA%BA%E6%84%9F%E6%83%B3" class="hash-link" aria-label="个人感想的直接链接" title="个人感想的直接链接" translate="no">​</a></h2>
<ul>
<li class="">Tailwind 提高的是<strong>表达样式的速度</strong>，不自动等于设计系统</li>
<li class="">复杂视觉稿仍需要 token 与组件边界，否则类名会失控</li>
<li class="">和 CSS Modules / Scoped 不是谁取代谁：隔离诉求强时可以混用</li>
</ul>
<p>一句话：<strong>用原子类写得快，用组件和 token 写得稳。</strong></p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="CSS" term="CSS"/>
        <category label="TailwindCSS" term="TailwindCSS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Axios 封装中的 TypeScript 实践]]></title>
        <id>https://docs.asmu1n.com/blog/2024/11/06/TypeScript</id>
        <link href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript"/>
        <updated>2024-11-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[用 axios 封装练习请求/响应泛型，不是「类型体操」专题。]]></summary>
        <content type="html"><![CDATA[<p>用 axios 封装练习请求/响应泛型，不是「类型体操」专题。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p>本文借助 <code>axios</code> 请求封装作为实战用例，涉及 <code>TypeScript</code>、<code>axios</code>、<code>React</code>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心代码">核心代码<a href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript#%E6%A0%B8%E5%BF%83%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="核心代码的直接链接" title="核心代码的直接链接" translate="no">​</a></h2>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> axios </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'axios'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> AxiosResponse</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> AxiosRequestConfig </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'axios'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> toast </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react-toastify'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">IData</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name"> </span><span class="token class-name operator" style="color:#393A34">=</span><span class="token class-name"> </span><span class="token class-name builtin">any</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    success</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    data</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    token</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> baseURL </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/api'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> Axios </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> axios</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    baseURL</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 登录/注册等无需带 token 的接口（不要把管理端路径误放进白名单）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> noAuthUrls </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'/user/login'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/user/register'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Axios</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">interceptors</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    config </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">noAuthUrls</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">url </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> token </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> localStorage</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getItem</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'token'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            toast</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">warn</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'请先登录'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> progress</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">setTimeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                window</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">location</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">href </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2500</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'请先登录'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Authorization </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> token</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    error </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        toast</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Axios</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">interceptors</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">response</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AxiosResponse</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">IData</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">success</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        toast</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// reject Error，方便下游读 e.message</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    error </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        toast</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">request</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AxiosRequestConfig</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">IData</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> response </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> Axios</span><span class="token punctuation" style="color:#393A34">.</span><span class="token generic-function function" style="color:#d73a49">request</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">IData</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="axios-类型剖析">axios 类型剖析<a href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript#axios-%E7%B1%BB%E5%9E%8B%E5%89%96%E6%9E%90" class="hash-link" aria-label="axios 类型剖析的直接链接" title="axios 类型剖析的直接链接" translate="no">​</a></h2>
<p><code>axios</code>涉及的<code>api</code>太多,我就聚焦在<code>核心代码</code>中涉及到的</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="axiosrequestconfig">AxiosRequestConfig<a href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript#axiosrequestconfig" class="hash-link" aria-label="AxiosRequestConfig的直接链接" title="AxiosRequestConfig的直接链接" translate="no">​</a></h3>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">AxiosRequestConfig</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">D</span><span class="token class-name"> </span><span class="token class-name operator" style="color:#393A34">=</span><span class="token class-name"> </span><span class="token class-name builtin">any</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  url</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  method</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Method </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  baseURL</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  transformRequest</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AxiosRequestTransformer </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> AxiosRequestTransformer</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  transformResponse</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AxiosResponseTransformer </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> AxiosResponseTransformer</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  headers</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">RawAxiosRequestHeaders </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain"> MethodsHeaders</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> AxiosHeaders</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  params</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  data</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">D</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  timeout</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> Milliseconds</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  timeoutErrorMessage</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  withCredentials</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  adapter</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AxiosAdapterConfig </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> AxiosAdapterConfig</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  auth</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AxiosBasicCredentials</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  responseType</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> ResponseType</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>上面是<code>AxiosRequestConfig</code>的定义,我仅截取了一部分作为说明。</p>
<p>这里我们可以看出接口定义了一个<code>D</code>泛型来约束我们所传入<code>data</code>的类型,并且默认它为<code>any</code>类型除非我们显式约束它。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="axiosresponse">AxiosResponse<a href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript#axiosresponse" class="hash-link" aria-label="AxiosResponse的直接链接" title="AxiosResponse的直接链接" translate="no">​</a></h3>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">AxiosResponse</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name"> </span><span class="token class-name operator" style="color:#393A34">=</span><span class="token class-name"> </span><span class="token class-name builtin">any</span><span class="token class-name punctuation" style="color:#393A34">,</span><span class="token class-name"> </span><span class="token class-name constant" style="color:#36acaa">D</span><span class="token class-name"> </span><span class="token class-name operator" style="color:#393A34">=</span><span class="token class-name"> </span><span class="token class-name builtin">any</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  statusText</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> RawAxiosResponseHeaders </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> AxiosResponseHeaders</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  config</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> InternalAxiosRequestConfig</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">D</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  request</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>这里接受两个泛型,分别表示<code>data</code>和<code>config</code>的类型,默认它们为<code>any</code>类型。
其中<code>InternalAxiosRequestConfig</code>是<code>AxiosRequestConfig</code>的拓展</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">InternalAxiosRequestConfig</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">D</span><span class="token class-name"> </span><span class="token class-name operator" style="color:#393A34">=</span><span class="token class-name"> </span><span class="token class-name builtin">any</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">AxiosRequestConfig</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">D</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AxiosRequestHeaders</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="思路">思路<a href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript#%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="思路的直接链接" title="思路的直接链接" translate="no">​</a></h2>
<p><em>首先,明确我们的目的:</em></p>
<ol>
<li class=""><strong>能够对接口请求进行统一请求、响应拦截.</strong></li>
<li class=""><strong>编写请求代码时能够约束或者说定义接口所返回的数据结构</strong></li>
<li class=""><strong>大部分情况下<code>TS</code>能够提供较好的类型提示</strong></li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="请求拦截">请求拦截<a href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript#%E8%AF%B7%E6%B1%82%E6%8B%A6%E6%88%AA" class="hash-link" aria-label="请求拦截的直接链接" title="请求拦截的直接链接" translate="no">​</a></h3>
<p>设置了权限拦截,对没有通过身份校验的人员退回验证界面。不是本文的重点,就不赘述了。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="响应拦截">响应拦截⭐<a href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript#%E5%93%8D%E5%BA%94%E6%8B%A6%E6%88%AA" class="hash-link" aria-label="响应拦截⭐的直接链接" title="响应拦截⭐的直接链接" translate="no">​</a></h3>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">IData</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name constant" style="color:#36acaa">T</span><span class="token class-name"> </span><span class="token class-name operator" style="color:#393A34">=</span><span class="token class-name"> </span><span class="token class-name builtin">any</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    success</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    data</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">T</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    token</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">response</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AxiosResponse</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">IData</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">success</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> response</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        toast</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    error </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        toast</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">reject</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>上面我们提到了 <code>AxiosResponse</code> 接受两个泛型，分别表示 <code>data</code> 和 <code>config</code> 的类型，默认是 <code>any</code>。
这里只约束 <code>data</code>，把 <code>IData</code> 作为第一个泛型传入。<code>IData</code> 也是泛型，用来约束业务 <code>data</code> 字段的结构。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="导出封装后的请求方法">导出封装后的请求方法⭐⭐⭐<a href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript#%E5%AF%BC%E5%87%BA%E5%B0%81%E8%A3%85%E5%90%8E%E7%9A%84%E8%AF%B7%E6%B1%82%E6%96%B9%E6%B3%95" class="hash-link" aria-label="导出封装后的请求方法⭐⭐⭐的直接链接" title="导出封装后的请求方法⭐⭐⭐的直接链接" translate="no">​</a></h3>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">request</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> AxiosRequestConfig</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">IData</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token constant" style="color:#36acaa">T</span><span class="token operator" style="color:#393A34">&gt;&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> Response </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> Axios</span><span class="token punctuation" style="color:#393A34">.</span><span class="token generic-function function" style="color:#d73a49">request</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">IData</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> Response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>后续的接口请求都通过<code>request</code>去完成
使用案例:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> request </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'.'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">addProduct</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name operator" style="color:#393A34">=</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">any</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">formData</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> FormData</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">request</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/product/add'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> formData</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> method</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'post'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">getProductList</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name operator" style="color:#393A34">=</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">any</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">request</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name constant" style="color:#36acaa">T</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/product/list'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> method</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'get'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> addProduct</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">getProductList </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> response </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">addProduct</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name keyword" style="color:#00009f">null</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">formData</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">//这里&lt;null&gt;其实就是data的类型,其他类型都是固定不变的</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">//null</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="注意项">注意项⭐⭐<a href="https://docs.asmu1n.com/blog/2024/11/06/TypeScript#%E6%B3%A8%E6%84%8F%E9%A1%B9" class="hash-link" aria-label="注意项⭐⭐的直接链接" title="注意项⭐⭐的直接链接" translate="no">​</a></h3>
<p><strong>要注意我们在导出的<code>request</code>方法是做了一层数据解构,只返回了<code>AxiosResponse</code>中的<code>data</code>字段</strong>,也就是实际上后续的请求方法获取到的数据是<code>data</code>字段的数据,<code>Response</code>和<code>response</code>并不一样。大部分情况我们都是直接使用服务端返回给我们的数据结构去完成业务逻辑。而解构后的<code>response</code>就是服务端返回的数据。</p>
<p><em>服务端返回的数据（Express 等）</em></p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> success</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'商品添加成功'</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">flowchart LR</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Call[request T config] --&gt; Axios[axios 实例]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Axios --&gt; ReqI[请求拦截: token / 白名单]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ReqI --&gt; Net[网络]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Net --&gt; ResI[响应拦截: IData]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ResI --&gt; Data[返回 IData T]</span><br></div></code></pre></div></div>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="TypeScript" term="TypeScript"/>
        <category label="网络" term="网络"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[现代化工程中CSS样式方案]]></title>
        <id>https://docs.asmu1n.com/blog/2024/10/24/CSS_scheme</id>
        <link href="https://docs.asmu1n.com/blog/2024/10/24/CSS_scheme"/>
        <updated>2024-10-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文讨论的是CSS方案是从项目工程化的角度出发。]]></summary>
        <content type="html"><![CDATA[<p>本文讨论的是<code>CSS</code>方案是从项目工程化的角度出发。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="内联样式">内联样式<a href="https://docs.asmu1n.com/blog/2024/10/24/CSS_scheme#%E5%86%85%E8%81%94%E6%A0%B7%E5%BC%8F" class="hash-link" aria-label="内联样式的直接链接" title="内联样式的直接链接" translate="no">​</a></h2>
<p>最简单粗暴的<code>CSS</code>样式方案,每一个前端开发学习历程必经之路。直接将样式书写在元素上,能够高度精细化的去控制每一个元素的样式。</p>
<p><strong>优点:</strong></p>
<ul>
<li class="">完美实现样式的隔离</li>
<li class="">直接书写样式,能够很好地动态调整样式</li>
</ul>
<p><strong>缺点:</strong></p>
<ul>
<li class="">极差的维护性</li>
<li class="">极差的复用性</li>
<li class="">无法使用<code>伪类</code>和<code>伪元素</code></li>
</ul>
<p>在一个稍具规模的项目里,内联样式是无法作为一个好的方案的。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="css-modules">CSS Modules<a href="https://docs.asmu1n.com/blog/2024/10/24/CSS_scheme#css-modules" class="hash-link" aria-label="CSS Modules的直接链接" title="CSS Modules的直接链接" translate="no">​</a></h2>
<p><code>CSS Modules</code> 能够实现我们在编写<code>CSS</code>样式文件能够保持原有的习惯的同时很好地解决了样式隔离的问题</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">styles</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'../assets/styles/page.module.css'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token spread operator" style="color:#393A34">...</span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">div className</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">styles</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">demo</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token number" style="color:#36acaa">233</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">div</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">div</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            className</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">styles</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">container</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c"> </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">isActive </span><span class="token template-string interpolation operator" style="color:#393A34">?</span><span class="token template-string interpolation"> styles</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">active</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation operator" style="color:#393A34">:</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation string" style="color:#e3116c">''</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            onClick</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">handleClick</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token maybe-class-name">Click</span><span class="token plain"> me to change color</span><span class="token operator" style="color:#393A34">!</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">div</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>优点:</p>
<ul>
<li class="">可维护性好: 样式集中,保持原有<code>CSS</code>的阅读、编写习惯</li>
<li class="">高复用性</li>
<li class="">样式隔离</li>
</ul>
<p>缺点:</p>
<ul>
<li class="">动态样式编写比较麻烦</li>
<li class="">需要额外的构建步骤   (在当今前端开发普遍都使用<code>构建工具</code>的情况,这点是可以忽略的而且除了<code>内联样式</code>,其他方案都是借助<code>构建工具</code>实现的)</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="css-in-js">CSS in JS<a href="https://docs.asmu1n.com/blog/2024/10/24/CSS_scheme#css-in-js" class="hash-link" aria-label="CSS in JS的直接链接" title="CSS in JS的直接链接" translate="no">​</a></h2>
<p><code>CSS in JS</code>本身也分化成许多种方案,比较火的有<code>Styled Components</code>,<code>Emotion</code>本文只以<code>Styled Components</code>为例。它使用 <code>ES6</code> 的模板字符串语法来定义<code>组件级别的样式</code>。通过使用 <code>Styled Components</code>，你可以将组件的样式与其逻辑紧密结合，使得样式更加模块化和可维护。</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useState </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">styled</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'styled-components'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 定义一个带有样式的容器组件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">Container</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> styled</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">div</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css comment" style="color:#999988;font-style:italic">/* 根据 props.isActive 设置颜色 */</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">color</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string css language-css interpolation parameter">props</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation arrow operator" style="color:#393A34">=&gt;</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation punctuation" style="color:#393A34">(</span><span class="token template-string css language-css interpolation">props</span><span class="token template-string css language-css interpolation punctuation" style="color:#393A34">.</span><span class="token template-string css language-css interpolation property-access">isActive</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation operator" style="color:#393A34">?</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation string" style="color:#e3116c">'red'</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation operator" style="color:#393A34">:</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation string" style="color:#e3116c">'blue'</span><span class="token template-string css language-css interpolation punctuation" style="color:#393A34">)</span><span class="token template-string css language-css interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">background-color</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css color">lightgray</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">padding</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css number" style="color:#36acaa">10</span><span class="token template-string css language-css unit">px</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">cursor</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> pointer</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">transition</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> color </span><span class="token template-string css language-css number" style="color:#36acaa">0.3</span><span class="token template-string css language-css unit">s</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css"></span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">Button</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> styled</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">button</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css comment" style="color:#999988;font-style:italic">/* 根据 props.primary 设置按钮样式 */</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">background</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string css language-css interpolation parameter">props</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation arrow operator" style="color:#393A34">=&gt;</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation punctuation" style="color:#393A34">(</span><span class="token template-string css language-css interpolation">props</span><span class="token template-string css language-css interpolation punctuation" style="color:#393A34">.</span><span class="token template-string css language-css interpolation property-access">$primary</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation operator" style="color:#393A34">?</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation string" style="color:#e3116c">'#BF4F74'</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation operator" style="color:#393A34">:</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation string" style="color:#e3116c">'white'</span><span class="token template-string css language-css interpolation punctuation" style="color:#393A34">)</span><span class="token template-string css language-css interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">color</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string css language-css interpolation parameter">props</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation arrow operator" style="color:#393A34">=&gt;</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation punctuation" style="color:#393A34">(</span><span class="token template-string css language-css interpolation">props</span><span class="token template-string css language-css interpolation punctuation" style="color:#393A34">.</span><span class="token template-string css language-css interpolation property-access">$primary</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation operator" style="color:#393A34">?</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation string" style="color:#e3116c">'white'</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation operator" style="color:#393A34">:</span><span class="token template-string css language-css interpolation"> </span><span class="token template-string css language-css interpolation string" style="color:#e3116c">'#BF4F74'</span><span class="token template-string css language-css interpolation punctuation" style="color:#393A34">)</span><span class="token template-string css language-css interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">font-size</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css number" style="color:#36acaa">1</span><span class="token template-string css language-css unit">em</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">margin</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css number" style="color:#36acaa">1</span><span class="token template-string css language-css unit">em</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">padding</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css number" style="color:#36acaa">0.25</span><span class="token template-string css language-css unit">em</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css number" style="color:#36acaa">1</span><span class="token template-string css language-css unit">em</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">border</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css number" style="color:#36acaa">2</span><span class="token template-string css language-css unit">px</span><span class="token template-string css language-css"> solid </span><span class="token template-string css language-css hexcode color">#bf4f74</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css">    </span><span class="token template-string css language-css property" style="color:#36acaa">border-radius</span><span class="token template-string css language-css punctuation" style="color:#393A34">:</span><span class="token template-string css language-css"> </span><span class="token template-string css language-css number" style="color:#36acaa">3</span><span class="token template-string css language-css unit">px</span><span class="token template-string css language-css punctuation" style="color:#393A34">;</span><span class="token template-string css language-css"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string css language-css"></span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:#d73a49">StyledComponentsStyles</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">isActive</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setIsActive</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">handleClick</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setIsActive</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">isActive</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">main</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Container</span><span class="token plain"> isActive</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">isActive</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token maybe-class-name">Click</span><span class="token plain"> me to change color</span><span class="token operator" style="color:#393A34">!</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token maybe-class-name">Container</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Button</span><span class="token plain"> onClick</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">handleClick</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> $primary</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">isActive</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token maybe-class-name">Primary</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token maybe-class-name">Button</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">main</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token maybe-class-name">StyledComponentsStyles</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>优点:</strong></p>
<ul>
<li class="">模块化:每个组件的样式都与组件本身紧密结合,自然而然避免了样式冲突</li>
<li class="">动态样式: 借助<code>JS</code>能够很简单地实现高度复杂的动态样式 --- <em>(这也是笔者认为它最大的优势)</em></li>
<li class="">可维护性: 与<code>JS</code>逻辑紧密结合,方便维护</li>
<li class="">自动前缀: 自动添加浏览器前缀,使样式兼容更多浏览器</li>
</ul>
<p><strong>缺点:</strong></p>
<ul>
<li class="">与传统的<code>CSS</code>样式编写方式差别较大,需要一定的学习成本</li>
<li class=""><code>CSS in JS</code> 本质上是使用<code>JS</code>运行时生成<code>CSS</code>,性能会有所影响</li>
<li class="">将<code>CSS</code>抽象成组件的概念,一定程度上混淆了组件原有的含义,代码可读性有所下降---(笔者最不能接受的一点)</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="原子化css">原子化CSS<a href="https://docs.asmu1n.com/blog/2024/10/24/CSS_scheme#%E5%8E%9F%E5%AD%90%E5%8C%96css" class="hash-link" aria-label="原子化CSS的直接链接" title="原子化CSS的直接链接" translate="no">​</a></h2>
<p>比较著名的原子化CSS方案有<code>Tailwind CSS</code>和<code>Windi CSS</code>,本文以<code>Tailwind CSS</code>为例。</p>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex-shrink-0</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">img</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">h-12 w-12</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">src</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">/img/logo.svg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">alt</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">ChitChat Logo</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-xl font-medium text-black</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">ChitChat</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-gray-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">You have a new message!</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>原子化<code>CSS</code>方案,是将<code>CSS</code>的样式拆分成一个个的原子,然后通过组合这些原子来实现复杂的样式。</p>
<p>通俗点来说,你不需要考虑新增一个<code>CSS</code>类去实现你想要的样子,而是通过一个个<code>原子CSS类</code>像积木一样拼出你想要的样子</p>
<p><strong>优点:</strong></p>
<ul>
<li class="">通过统一的工具类体系降低自定义类名冲突（工具类本身是全局共享的，并不是 CSS Modules 那种文件级隔离）</li>
<li class="">熟悉写法的情况,编写<code>CSS</code>的效率会大大提高----(因为不需要一个个编写<code>CSS</code>属性名和属性值)</li>
</ul>
<p><strong>缺点:</strong></p>
<ul>
<li class="">较高的学习成本,需要熟悉原子化的写法</li>
<li class="">难以普及,需要团队的共同努力才能形成一套统一的原子化方案</li>
<li class="">可读性较差,在普遍支持直接编写嵌套的<code>CSS</code>样式情况下,使用原子化方案的可读性会有所下降</li>
<li class="">需要<code>IDE</code>插件支持才有较好的编写体验</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scoped">Scoped<a href="https://docs.asmu1n.com/blog/2024/10/24/CSS_scheme#scoped" class="hash-link" aria-label="Scoped的直接链接" title="Scoped的直接链接" translate="no">​</a></h2>
<p><strong>Scoped CSS</strong>（以 Vue SFC 的 <code>scoped</code> 最为典型，并不是 Vue 独有思路）本质是编译期给标签和选择器加上作用域属性（如 <code>data-v-xxx</code>），用属性选择器模拟样式隔离；该转换通常由编译工具链（如 Vue 编译器 / PostCSS 插件）完成。</p>
<p>如果需要在父组件中影响子组件样式，Vue 提供了 <code>:deep()</code> 做样式穿透。</p>
<p><em>常用于我们使用UI组件库时,我们需要对组件库的样式进行修改。</em></p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector class" style="color:#00009f">.b</span><span class="token selector" style="color:#00009f"> </span><span class="token selector pseudo-class" style="color:#00009f">:deep</span><span class="token selector punctuation" style="color:#393A34">(</span><span class="token selector class" style="color:#00009f">.ui</span><span class="token selector punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    .....</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    .....</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* 对ui类进行样式穿透 */</span><br></div></code></pre></div></div>
<p><strong>优点:</strong></p>
<ul>
<li class="">样式隔离,不会造成样式冲突</li>
<li class="">提供能够主动影响子组件的样式的能力</li>
<li class="">几乎等同于原生<code>CSS</code>的编写方式</li>
</ul>
<p><strong>缺点:</strong></p>
<ul>
<li class="">以 Vue SFC 的 <code>scoped</code> 最常见；其他技术栈更常用 CSS Modules、CSS-in-JS 等达到类似隔离</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="题外话">题外话<a href="https://docs.asmu1n.com/blog/2024/10/24/CSS_scheme#%E9%A2%98%E5%A4%96%E8%AF%9D" class="hash-link" aria-label="题外话的直接链接" title="题外话的直接链接" translate="no">​</a></h2>
<p>哪种方案更好?
每种方案都有自己的优缺点(除了<code>内联样式</code>基本排除工程化开发),需要团队的共同协商达成共识才能很好去进行开发流程。对于团队而言,与其在方案中一争高下,更高效地其实是选择大家都能喜欢或者说能接受的选项。</p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="CSS" term="CSS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Vite的HMR实现过程]]></title>
        <id>https://docs.asmu1n.com/blog/2024/10/24/Vite_HMR</id>
        <link href="https://docs.asmu1n.com/blog/2024/10/24/Vite_HMR"/>
        <updated>2024-10-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Vite热更新流程]]></summary>
        <content type="html"><![CDATA[<p><code>Vite</code>热更新流程</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://docs.asmu1n.com/blog/2024/10/24/Vite_HMR#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接" translate="no">​</a></h2>
<p><em><code>Vite</code>的<code>HMR</code>(热更新)仅限于在开发环境下使用,故以下默认都处于开发环境下。</em></p>
<p><strong><code>Vite</code>的实现离不开现代浏览器原生支持<code>ESM</code>的特性。</strong></p>
<p><em>当声明一个<code>script</code> 标签类型为<code>module</code>时,浏览器将对其内部的<code>import</code>引用发起<code>HTTP</code>请求获取模块内容。获取到内容后再进行执行</em></p>
<p><strong>值得注意的是：浏览器只会对用到的模块发起 HTTP 请求，所以 Vite 开发期不必先打包全项目，而是按需编译浏览器请求到的模块。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="开发模式下-vite-的工作流程">开发模式下 Vite 的工作流程<a href="https://docs.asmu1n.com/blog/2024/10/24/Vite_HMR#%E5%BC%80%E5%8F%91%E6%A8%A1%E5%BC%8F%E4%B8%8B-vite-%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="开发模式下 Vite 的工作流程的直接链接" title="开发模式下 Vite 的工作流程的直接链接" translate="no">​</a></h2>
<ol>
<li class="">启动开发服务器，并<strong>建立</strong> WebSocket，用于与浏览器双向实时通信。</li>
<li class="">浏览器通过 HTTP 请求模块时，开发服务器拦截请求，对对应源码做转换（依赖预构建等多用 esbuild），把浏览器可执行的模块返回。</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Vite 对某个模块进行更新" src="https://docs.asmu1n.com/assets/images/ESM-868fbadf1d2cfe3baff3b1d11446aa72.png" width="747" height="325" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hmr-的工作流程">HMR 的工作流程<a href="https://docs.asmu1n.com/blog/2024/10/24/Vite_HMR#hmr-%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="HMR 的工作流程的直接链接" title="HMR 的工作流程的直接链接" translate="no">​</a></h2>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">sequenceDiagram</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant FS as 文件系统</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant Vite as Vite Dev Server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant WS as WebSocket</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    participant Browser as 浏览器</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    FS-&gt;&gt;Vite: 文件变更</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Vite-&gt;&gt;Vite: 计算模块图 / 找 HMR boundary</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Vite-&gt;&gt;WS: 推送更新模块信息</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    WS-&gt;&gt;Browser: 通知热更新</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    alt 边界 accept 成功</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Browser-&gt;&gt;Browser: 替换模块，保留状态</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    else 无法热替换</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Browser-&gt;&gt;Browser: 整页 reload</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    end</span><br></div></code></pre></div></div>
<ul>
<li class="">Vite 监听项目文件变化</li>
<li class="">变更后沿依赖图分析影响范围；能热替换的模块走 <code>import.meta.hot.accept</code> 边界</li>
<li class="">通过 WebSocket 通知浏览器重新请求/替换相关模块；边界失效则 full reload</li>
</ul>
<p>HMR <strong>只服务于开发环境</strong>，与生产 Rollup 打包是两条路径。</p>
<p><img decoding="async" loading="lazy" alt="Vite 的流程图" src="https://docs.asmu1n.com/assets/images/Vite-9d5f95eee2ea185fea3bf47c40e479bb.png" width="673" height="721" class="img_ev3q"></p>]]></content>
        <author>
            <name>Jason Rong</name>
        </author>
        <category label="Vite" term="Vite"/>
    </entry>
</feed>