<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>楷鹏 Blog</title>
        <link>https://wukaipeng.com/en/blog</link>
        <description>楷鹏 Blog</description>
        <lastBuildDate>Sun, 21 Jun 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[The Attention Revolution: WeChat]]></title>
            <link>https://wukaipeng.com/en/blog/attention-revolution</link>
            <guid>https://wukaipeng.com/en/blog/attention-revolution</guid>
            <pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Attention overload has become a new kind of modern-day "sub-health."]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2026/06/21-171801-tm4LDr-cover-twitter-1782033450558.png" alt="" class="img_ev3q"></p>
<p><strong>Attention overload</strong> has become a new kind of modern-day <strong>"sub-health."</strong></p>
<p>On average, a person encounters <strong>6,000 to 10,000 ads</strong> every day, whether they mean to or not.</p>
<p>That number is <strong>twice</strong> what it was 20 years ago.</p>
<p>You can see how much money is involved by looking at companies like Tencent, whose ad business has grown from around <strong>200 million</strong> to now being worth <strong>hundreds of billions</strong>.</p>
<p>To protect my attention, I recently made some changes. I started with WeChat, the app I rely on most in daily life.</p>
<p>WeChat brings together chat, official accounts, video accounts, livestreams, music, shopping, games, and even mini-programs—essentially a <strong>full mini operating system</strong> in one app. But at the beginning, it was only for logging in, adding friends, and sending messages.</p>
<p>Because it is deeply tied to personal relationships and social networks, WeChat has become almost impossible to fully avoid in everyday life. Since that is the case, I decided to <strong>take a scalpel to it and make some changes</strong>:</p>
<p><strong>First</strong>, I unfollowed all official accounts and service accounts, and turned off personalized recommendations and video account recommendations.</p>
<p><strong>Second</strong>, I deleted <strong>more than 200 friends</strong> and left over <strong>40 group chats</strong>, then folded the remaining conversations away.</p>
<p><strong>Third</strong>, I turned off every feature in the <strong>Discover</strong> tab, including Moments, video accounts, livestreams, <strong>"Top Stories,"</strong> search, and more.</p>
<p><strong>Fourth</strong>, I disabled all services, including credit card payments, phone top-ups, utility bills, travel services, hotels, and homestays.</p>
<p><strong>Fifth</strong>, I turned off WeChat's <strong>focus notifications, app icon badges, floating notifications, lock screen notifications, sound alerts, and vibration</strong>—leaving only incoming calls.</p>
<p>After doing all of this, I got a kind of <strong>"lite"</strong> version of WeChat. The only regrettable part is that WeChat still doesn't let me turn off the profile sections for <strong>status, Moments, Works, Store, Wallet, and stickers</strong>.</p>
<p>Of course, this process involved a period of withdrawal. I used to love scrolling through Moments, official accounts, and video accounts. But once the constant stream of messages and information attacks was reduced, I realized that the attention WeChat had stolen from me was coming back. And now I’m already used to it—I don't want to go back.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Less-Known but Powerful React Hook: useSyncExternalStore]]></title>
            <link>https://wukaipeng.com/en/blog/use-sync-external-store</link>
            <guid>https://wukaipeng.com/en/blog/use-sync-external-store</guid>
            <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In general, React state comes from inside the component itself, such as values created with useState.]]></description>
            <content:encoded><![CDATA[<p>In general, React state comes from inside the component itself, such as values created with <code>useState</code>.</p>
<p>But sometimes, the state comes from somewhere else. As shown in the classic example from the React docs, we may need to track whether the browser is online:</p>
<p>If the network is available, show ✅ Online; otherwise show ❌ Disconnected.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2026/04/20-231445-S5GJtQ-image-20260420231445629.png" alt="Online" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2026/04/20-231537-VF0caA-image-20260420231536896.png" alt="Offline" class="img_ev3q"></p>
<p><em>Use Chrome DevTools &gt; Network to simulate online/offline states.</em></p>
<p>Here, the <strong>online status</strong> comes from the external value <code>navigator.onLine</code>, so <code>useSyncExternalStore</code> is a perfect fit:</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"><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">ChatIndicator</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> isOnline </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">subscribe</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 punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">&lt;</span><span class="token plain">h1</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">isOnline </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'✅ Online'</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">'❌ Disconnected'</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">h1</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> navigator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">onLine</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">subscribe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">callback</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></span><span 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">'online'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> callback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">'offline'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> callback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span 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">removeEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'online'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> callback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">removeEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'offline'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> callback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>As you can see, the basic setup for <code>useSyncExternalStore</code> includes a subscribe function and a getSnapshot function:</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"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> isOnline </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">subscribe</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 punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div>
<p>If you look closely at the subscribe function, it runs logic inside the function, calls <code>callback</code>, and finally returns a cleanup function. This feels very similar to the usual effect hook <code>useEffect</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"><span 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">subscribe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">callback</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></span><span 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">'online'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> callback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">'offline'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> callback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span 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">removeEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'online'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> callback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">removeEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'offline'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> callback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>That <code>callback</code> is important: React calls it like a delivery person, and it asks <code>getSnapshot</code> for the latest value. If the value has changed, React immediately updates the component.</p>
<!-- -->
<p>At this point, you might think that <code>useEffect</code> could also do the same thing:</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"><span 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"> useState</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> useEffect </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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">ChatIndicator</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></span><span 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">isOnline</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setIsOnline</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 plain">navigator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">onLine</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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 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></span><span 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">handleOnline</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 operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setIsOnline</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></span><span 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">handleOffline</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 operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setIsOnline</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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">'online'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handleOnline</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">'offline'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handleOffline</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">removeEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'online'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handleOnline</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">removeEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'offline'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handleOffline</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span 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 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">&lt;</span><span class="token plain">h1</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">isOnline </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'✅ Online'</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">'❌ Disconnected'</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">h1</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>If <code>useEffect</code> can do the same thing, is <code>useSyncExternalStore</code> just unnecessary extra work?</p>
<p>This is where React tearing becomes important. Consider a simple component like this:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2026/04/23-234243-jd51P7-20260423-234140.gif" alt="React Tearing Demo" class="img_ev3q"></p>
<p>If you look carefully, when the page first opens, these counters show different values such as 218, 219, and 220, and then they all eventually become 221.</p>
<p>In reality, all of these counters are reading from the same external data source. In theory, they should always stay in sync.</p>
<p>But React 18’s concurrent rendering model doesn’t always behave politely.</p>
<!-- -->
<p>Because rendering happens concurrently, some components may update to the latest external value while others lag behind.</p>
<p>That lag creates the visual effect of components being “torn” apart.</p>
<p>This is exactly why <code>useSyncExternalStore</code> exists. It calls <code>getSnapshot</code> during rendering to check whether the value is still current. If not, React discards the current render and performs a <strong>synchronous, non-blocking</strong> rerender with the latest value:</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[内部分享：开发利器 React Query]]></title>
            <link>https://wukaipeng.com/en/blog/2025/11/05/2025/ React Qeury</link>
            <guid>https://wukaipeng.com/en/blog/2025/11/05/2025/ React Qeury</guid>
            <pubDate>Wed, 05 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[背景]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="背景">背景<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="Direct link to 背景" title="Direct link to 背景">​</a></h2>
<p><code>@tanstack/react-query</code> （下称 RQ）在 GitHub 上已经累积了 4w+ 的 ⭐，称得上 React 技术栈中必不可少的工具。</p>
<p>它并不像 Zustand 这些通用的状态管理库，而是<strong>针对请求这一场景，做了相关的深度优化和功能定制</strong>。</p>
<p>RQ 在当前项目已经引入将近两个月，但是使用情况并不多，障碍可能来自于 RQ 的学习曲线和心智认知，所以开展本次 RQ 分享，让团队各位更多地了解 RQ 的能力，进而利用好 RQ，提高开发效率。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="rq-和-axios-的关系">RQ 和 Axios 的关系<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#rq-%E5%92%8C-axios-%E7%9A%84%E5%85%B3%E7%B3%BB" class="hash-link" aria-label="Direct link to RQ 和 Axios 的关系" title="Direct link to RQ 和 Axios 的关系">​</a></h2>
<p>目前我们的项目中使用 Axios 作为请求器，而 RQ 并不是用来替代 Axios，而是用来增强 Axios 的。</p>
<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"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 👉 这是用 Axios 去实现的获取站内信未读总数的接口方法</span><span class="token plain"></span><br></span><span 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">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getUnreadNotificationCount</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"> </span><span class="token builtin">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token builtin">number</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> clientApi</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 constant" style="color:#36acaa">URL</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">GET_UNREAD_NOTIFICATION_COUNT</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    fetchOptions</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></span><span class="token-line" style="color:#393A34"><span class="token plain">      experimental_throw_all_errors</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></span><span class="token-line" style="color:#393A34"><span class="token plain">      experimental_no_toast_when_error</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></span><span 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></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 👉 使用 RQ 通过 hook 封装的方式，去增强未读总信接口</span><span class="token plain"></span><br></span><span 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"> useQuery </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">'@tanstack/react-query'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">useUnreadNotificationCount</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 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></span><span 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">useQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    queryKey</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">'notification'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'unread-count'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 👉 RQ 最终调用我们提供给接口方法</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    queryFn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> getUnreadNotificationCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><br></span><span 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></span><span 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></span></code></pre></div></div>
<p>所以 RQ 其实是请求框架无关的工具，它可以和 Axios，GraphQL，或者原生 <code>fetch()</code> 方法等结合使用。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="功能一减少样板代码">功能一：减少样板代码<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E5%8A%9F%E8%83%BD%E4%B8%80%E5%87%8F%E5%B0%91%E6%A0%B7%E6%9D%BF%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="Direct link to 功能一：减少样板代码" title="Direct link to 功能一：减少样板代码">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="不使用-rq">不使用 RQ<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E4%B8%8D%E4%BD%BF%E7%94%A8-rq" class="hash-link" aria-label="Direct link to 不使用 RQ" title="Direct link to 不使用 RQ">​</a></h3>
<p>假设我们不使用 RQ，那么正常调用一个接口就是：</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"><span 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"> useState</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> useEffect </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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">NotificationBadge</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></span><span 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></span><span 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">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">isLoading</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setIsLoading</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">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">error</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setError</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 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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 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></span><span 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></span><span 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">fetchCount</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">async</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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setIsLoading</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></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> count </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">getUnreadNotificationCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setCount</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"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setError</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></span><span 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">err</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></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setError</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 punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">finally</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setIsLoading</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></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">fetchCount</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></span><span 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 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">isLoading</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 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">Loading</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">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></span><span 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"> </span><span class="token keyword" 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 plain">Error loading notifications</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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">&lt;</span><span class="token plain">div</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain">Unread</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">count</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">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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>如果这个接口在多个地方使用，我们需要封装为一个自定义 hook：</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"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 👉 创建自定义 hook</span><span class="token plain"></span><br></span><span 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"> useState</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> useEffect </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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useUnreadNotificationCount</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></span><span 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">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">isLoading</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setIsLoading</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">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">error</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setError</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 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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 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></span><span 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">fetchCount</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">async</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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setIsLoading</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></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> count </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">getUnreadNotificationCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setCount</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"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setError</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></span><span 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">err</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></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setError</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 punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">finally</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setIsLoading</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></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">fetchCount</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></span><span 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 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">count</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isLoading</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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">NotificationBadge</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></span><span 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"> isLoading</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 operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useUnreadNotificationCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">isLoading</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 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">Loading</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">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></span><span 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"> </span><span class="token keyword" 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 plain">Error loading notifications</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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">&lt;</span><span class="token plain">div</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain">Unread</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">count</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">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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="使用-rq">使用 RQ<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E4%BD%BF%E7%94%A8-rq" class="hash-link" aria-label="Direct link to 使用 RQ" title="Direct link to 使用 RQ">​</a></h3>
<p>使用 RQ 之后：</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"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 1. 用 RQ 封装自定义 hook</span><span class="token plain"></span><br></span><span 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"> useQuery </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">'@tanstack/react-query'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">useUnreadNotificationCount</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 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></span><span 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">useQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    queryKey</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">'notification'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'unread-count'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    queryFn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> getUnreadNotificationCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 2. 引用该 hook</span><span class="token plain"></span><br></span><span 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">NotificationBadge</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></span><span 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 operator" style="color:#393A34">:</span><span class="token plain"> count</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isLoading</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 operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useUnreadNotificationCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">isLoading</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 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">Loading</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">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></span><span 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"> </span><span class="token keyword" 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 plain">Error loading notifications</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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">&lt;</span><span class="token plain">div</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain">Unread</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">count</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">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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>RQ 相当于语法糖，简化了自定义 hook 的封装，可以大大减少我们写样板代码。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="功能二减少重复请求">功能二：减少重复请求<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E5%8A%9F%E8%83%BD%E4%BA%8C%E5%87%8F%E5%B0%91%E9%87%8D%E5%A4%8D%E8%AF%B7%E6%B1%82" class="hash-link" aria-label="Direct link to 功能二：减少重复请求" title="Direct link to 功能二：减少重复请求">​</a></h2>
<p>在我们的通知页中，中间菜单和右侧内容都有标题【点赞】以及未读数【3】，这里的未读数都是通过接口获取：</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/11/06-001511-aZvvkL-image-20251106001508753.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="不使用-rq-1">不使用 RQ<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E4%B8%8D%E4%BD%BF%E7%94%A8-rq-1" class="hash-link" aria-label="Direct link to 不使用 RQ" title="Direct link to 不使用 RQ">​</a></h3>
<p>那么两个地方引用了相同的数据，没有 RQ 的情况下，处理方式有：</p>
<p><strong>状态提升</strong>。把请求接口提升到他们的共同的父组件中。</p>
<ul>
<li>优点: 简单直接，单一数据源。</li>
<li>缺点: 父组件需要知道子组件的数据需求</li>
</ul>
<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"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:#d73a49">NotificationPage</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></span><span 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">unreadCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setUnreadCount</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">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">isLoading</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setIsLoading</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">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">fetchUnreadCount</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></span><span 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 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">fetchUnreadCount</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">async</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setIsLoading</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></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> count </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">getUnreadNotificationCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setUnreadCount</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"></span><br></span><span 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">catch</span><span class="token plain"> </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 punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span 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">err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">finally</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setIsLoading</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></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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></span><span 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">MiddleMenu</span><span class="token plain"> unreadCount</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">unreadCount</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 operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span 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">RightContent</span><span class="token plain"> unreadCount</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">unreadCount</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> onRefresh</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">fetchUnreadCount</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 operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p><strong>Context API</strong>：使用 <code>&lt;Context&gt;</code> 组件，跨层级共享数据。</p>
<ul>
<li>优点: 避免 Props 穿透（props drilling），组件解耦。</li>
<li>缺点: 任何 context 值变化会导致所有消费者重新渲染，需要额外的 Provider 包裹</li>
</ul>
<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"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">NotificationContext</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">createContext</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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">NotificationProvider</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter punctuation" style="color:#393A34">{</span><span class="token parameter"> children </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 punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span 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">unreadCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setUnreadCount</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">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">isLoading</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setIsLoading</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">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">fetchUnreadCount</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">async</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setIsLoading</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></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> count </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">getUnreadNotificationCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setUnreadCount</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"></span><br></span><span 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">catch</span><span class="token plain"> </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 punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span 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">err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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">finally</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setIsLoading</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></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">fetchUnreadCount</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></span><span 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 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">NotificationContext</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Provider</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> unreadCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isLoading</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">refetch</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> fetchUnreadCount </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></span><span class="token-line" style="color:#393A34"><span class="token plain">      </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"></span><br></span><span 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">NotificationContext</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Provider</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">MiddleMenu</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></span><span 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"> unreadCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isLoading </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">useContext</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">NotificationContext</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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 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">unreadCount</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">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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">RightContent</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></span><span 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"> unreadCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> refetch </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">useContext</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">NotificationContext</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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 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">unreadCount</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">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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p><strong>封装 Hook</strong>：把请求封装到 hook 中</p>
<ul>
<li>优点：共享请求状态</li>
<li>缺点：每个组件都会重复请求</li>
</ul>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="使用-rq-1">使用 RQ<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E4%BD%BF%E7%94%A8-rq-1" class="hash-link" aria-label="Direct link to 使用 RQ" title="Direct link to 使用 RQ">​</a></h3>
<p>使用 RQ 之后：</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"><span 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">useUnreadNotificationCount</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></span><span 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 function" style="color:#d73a49">useQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">queryKey</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">'notificationCount'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">queryFn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> getUnreadNotificationCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">MiddleMenu</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></span><span 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><span class="token literal-property property" style="color:#36acaa">data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> unreadCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> refresh </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">useUnreadNotificationCount</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></span><span 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 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">unreadCount</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">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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">RightContent</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></span><span 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><span class="token literal-property property" style="color:#36acaa">data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> unreadCount </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">useUnreadNotificationCount</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></span><span 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 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">unreadCount</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">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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p><strong>优点：</strong>
✅ <strong>自动去重</strong>: 两个组件同时挂载只发 1 次请求（<code>queryKey</code> 相同）<br>
<!-- -->✅ <strong>无需 Provider</strong>: 不需要包裹父组件<br>
<!-- -->✅ <strong>组件独立</strong>: 每个组件独立使用，不依赖父组件传递</p>
<blockquote>
<p>这里提到"两个组件同时挂载只会发起 1 次请求"，其原理就是 RQ 会缓存请求的数据。RQ 的很多功能都是基于缓存来操作。</p>
</blockquote>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="功能三重刷新">功能三：重刷新<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E5%8A%9F%E8%83%BD%E4%B8%89%E9%87%8D%E5%88%B7%E6%96%B0" class="hash-link" aria-label="Direct link to 功能三：重刷新" title="Direct link to 功能三：重刷新">​</a></h2>
<p>还是通知的例子，当用户打开"点赞"页面，在这里查看消息的时候，【未读数】需要动态更新：</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/11/06-001654-6ZWEZX-image-20251106001653820.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="不使用-rq-2">不使用 RQ<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E4%B8%8D%E4%BD%BF%E7%94%A8-rq-2" class="hash-link" aria-label="Direct link to 不使用 RQ" title="Direct link to 不使用 RQ">​</a></h3>
<p>没有 RQ 的情况下，我们需要对这么多个组件做数据联动，方案还是状态提升、Context，当然，还可以通过事件总线（EventEmitter）的方式：</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"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 定义一个事件总线通用方法：eventBus.js</span><span class="token plain"></span><br></span><span 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">EventBus</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span 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 punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span 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 property-access">events</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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><br></span><span 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 parameter">event</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> callback</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></span><span 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 keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">events</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 punctuation" 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 property-access">events</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">=</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></span><span 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 property-access">events</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 punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">push</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">callback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">emit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">event</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></span><span 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 keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">events</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 punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span 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 property-access">events</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 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 parameter">callback</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">callback</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 punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">off</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">event</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> callback</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></span><span 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 keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">events</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 punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span 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 property-access">events</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">=</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 property-access">events</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 punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">cb</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> cb </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> callback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">const</span><span class="token plain"> eventBus </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">EventBus</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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">LeftMenu</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></span><span 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">totalUnreadCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setTotalUnreadCount</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">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">fetchCount</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">async</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></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> count </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">getUnreadTotalNotificationCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setTotalUnreadCount</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"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">fetchCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span 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></span><span 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">handleRefresh</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 function" style="color:#d73a49">fetchCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    eventBus</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'notification:read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handleRefresh</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span 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 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></span><span class="token-line" style="color:#393A34"><span class="token plain">      eventBus</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">off</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'notification:read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handleRefresh</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span 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 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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 function" style="color:#d73a49">通知</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">totalUnreadCount</span><span class="token punctuation" style="color:#393A34">}</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">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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">MiddleMenu</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></span><span 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">unreadCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setUnreadCount</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">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">fetchCount</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">async</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></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> count </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">getUnreadNotificationCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setUnreadCount</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"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">fetchCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span 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></span><span 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">handleRefresh</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 function" style="color:#d73a49">fetchCount</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    eventBus</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'notification:read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handleRefresh</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span 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 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></span><span class="token-line" style="color:#393A34"><span class="token plain">      eventBus</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">off</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'notification:read'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> handleRefresh</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span 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 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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 function" style="color:#d73a49">点赞</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">unreadCount</span><span class="token punctuation" style="color:#393A34">}</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">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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">RightContent</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></span><span 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">handleMarkAsRead</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">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">id</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></span><span 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"> </span><span class="token function" style="color:#d73a49">markNotificationAsRead</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><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">    eventBus</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">emit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'notification:read'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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 onClick</span><span class="token operator" 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><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">handleMarkAsRead</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 punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      标记已读</span><br></span><span 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">button</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="使用-rq-2">使用 RQ<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E4%BD%BF%E7%94%A8-rq-2" class="hash-link" aria-label="Direct link to 使用 RQ" title="Direct link to 使用 RQ">​</a></h3>
<p>使用 RQ，利用它的 <code>invalidateQueries()</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"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 对于左侧菜单</span><span class="token plain"></span><br></span><span 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">LeftMenu</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></span><span 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><span class="token literal-property property" style="color:#36acaa">data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> unreadTotalCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> refresh </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">useQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">queryKey</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">'totalNotificationCount'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">queryFn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> getTotalUnreadNotificationCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">staleTime</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Infinite</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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 function" style="color:#d73a49">点赞</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">unreadCount</span><span class="token punctuation" style="color:#393A34">}</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">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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">MiddleMenu</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></span><span 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><span class="token literal-property property" style="color:#36acaa">data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> unreadCount </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">useQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">queryKey</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">'notificationCount'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">queryFn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> getUnreadNotificationCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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 function" style="color:#d73a49">点赞</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">unreadCount</span><span class="token punctuation" style="color:#393A34">}</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">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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">RightContent</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> queryClient </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useQueryClient</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></span><span 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><span class="token literal-property property" style="color:#36acaa">data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> unreadCount </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">useQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">queryKey</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">'notificationCount'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">queryFn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> getUnreadNotificationCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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><span class="token literal-property property" style="color:#36acaa">data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> notifications </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">useQuery</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">queryKey</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">'notifications'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'print-and-collect'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">queryFn</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 function" style="color:#d73a49">getNotifications</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'print-and-collect'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> markAsReadMutation </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useMutation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">mutationFn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> markNotificationAsRead</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">onSuccess</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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">      queryClient</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">invalidateQueries</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">queryKey</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">'totalNotificationCount'</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 plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      queryClient</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">invalidateQueries</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">queryKey</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">'notificationCount'</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 plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      queryClient</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">invalidateQueries</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">queryKey</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">'notifications'</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 plain"> </span><br></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">h2</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token function" style="color:#d73a49">点赞</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"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">unreadCount</span><span class="token punctuation" style="color:#393A34">}</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">h2</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">notifications</span><span class="token operator" style="color:#393A34">?.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">n</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></span><span 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 key</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">n</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">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">n</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">content</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><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">n</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">isRead</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span 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 onClick</span><span class="token operator" 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><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> markAsReadMutation</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mutate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">n</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 punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              标记已读</span><br></span><span 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">button</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span 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></span><span 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></span><span 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></span><span 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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p><strong>优势</strong>:
✅ <strong>零配置自动同步</strong>: <code>invalidateQueries</code> 一个方法搞定<br>
<!-- -->✅ <strong>组件完全解耦</strong>: <code>&lt;RightMenu&gt;</code>，<code>&lt;MiddleMenu&gt;</code> 和 <code>&lt;RightContent&gt;</code> 互不依赖<br>
<!-- -->✅ <strong>数据一致性</strong>: 始终从服务器获取最新数据<br>
<!-- -->✅ <strong>无需手动管理</strong>: 不需要回调、Context、事件总线<br>
<!-- -->✅ <strong>乐观更新</strong>: 可以先更新 UI，再同步服务器</p>
<blockquote>
<p>RQ 底层也是使用发布/订阅（pub/sub）设计模式。</p>
</blockquote>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="功能四乐观更新">功能四：乐观更新<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E5%8A%9F%E8%83%BD%E5%9B%9B%E4%B9%90%E8%A7%82%E6%9B%B4%E6%96%B0" class="hash-link" aria-label="Direct link to 功能四：乐观更新" title="Direct link to 功能四：乐观更新">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="什么是乐观更新">什么是乐观更新？<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E4%BB%80%E4%B9%88%E6%98%AF%E4%B9%90%E8%A7%82%E6%9B%B4%E6%96%B0" class="hash-link" aria-label="Direct link to 什么是乐观更新？" title="Direct link to 什么是乐观更新？">​</a></h3>
<p>乐观更新对应的是悲观更新，悲观更新也就是比较传统的做法——先请求 API，再更新 UI。<br>
<!-- -->而<strong>乐观更新是先更新 UI，再请求 API</strong>。</p>
<table><thead><tr><th>✅ 适合场景</th><th>❌ 不适合场景</th></tr></thead><tbody><tr><td>点赞/收藏（高概率成功）</td><td>支付、转账等关键操作</td></tr><tr><td>标记已读/未读</td><td>复杂的业务逻辑（服务器可能拒绝）</td></tr><tr><td>切换开关状态</td><td>数据结构复杂，难以预测结果</td></tr><tr><td>简单的增删改操作</td><td>需要服务器计算的数据（如库存扣减）</td></tr><tr><td>用户期望即时反馈的操作</td><td></td></tr></tbody></table>
<p>了解了乐观更新，我们来引用它去优化上一节提到的内容，先更新未读数的 UI，再请求 API：</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"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> markAsReadMutation </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useMutation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">mutationFn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> markNotificationAsRead</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ✅ 乐观更新：立即更新 UI，不等待服务器响应</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">onMutate</span><span class="token operator" style="color:#393A34">:</span><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 parameter">notificationId</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></span><span 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></span><span 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"> queryClient</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">cancelQueries</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">queryKey</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">'notificationCount'</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 plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> previousCount </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> queryClient</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getQueryData</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'notificationCount'</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">    queryClient</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">setQueryData</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'notificationCount'</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 parameter">old</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"> old </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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><br></span><span 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"> previousCount </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">onError</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">err</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> variables</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> context</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    queryClient</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">setQueryData</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'notificationCount'</span><span class="token punctuation" style="color:#393A34">]</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 property-access">previousCount</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">onSettled</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    queryClient</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">invalidateQueries</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">queryKey</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">'notificationCount'</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 plain"></span><br></span><span 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></span><span 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></span></code></pre></div></div>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="devtools">DevTools<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#devtools" class="hash-link" aria-label="Direct link to DevTools" title="Direct link to DevTools">​</a></h2>
<p>RQ 提供了一个 Devtools，可以作为调试使用，可以提升我们的开发效率。</p>
<p>可以在项目本地开发环境下集成 Devtools，在右下角这个浮动按钮：</p>
<p>TODO</p>
<p>对于 Devtools 来说，比较好用的主要有 Actions 和 Data Explorer，可以手动查看相关数据，也可以触发 RQ 的相关方法：</p>
<p>TODO</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="深入学习">深入学习<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0" class="hash-link" aria-label="Direct link to 深入学习" title="Direct link to 深入学习">​</a></h2>
<ol>
<li>
<p><strong>中文文档</strong><br>
<!-- -->目前官方文档只有英文，目前有挺多的相关中文文档镜像，比如：<br>
<a href="https://tanstack.com.cn/query/latest/docs/framework/react/overview" target="_blank" rel="noopener noreferrer">https://tanstack.com.cn/query/latest/docs/framework/react/overview</a><br>
<!-- -->也可以自行 google "tanstack 中文文档"</p>
</li>
<li>
<p><strong>沉浸式翻译</strong><br>
<!-- -->一款 web 插件，一键把英文翻译成中文，原汁原味，适合 RQ 原文档学习。<br>
<a href="https://chromewebstore.google.com/detail/bpoadfkcbjbfhfodiogcnhhhpibjhbnh?utm_source=item-share-cb" target="_blank" rel="noopener noreferrer">https://chromewebstore.google.com/detail/bpoadfkcbjbfhfodiogcnhhhpibjhbnh?utm_source=item-share-cb</a></p>
</li>
<li>
<p><strong>Zread</strong><br>
<!-- -->如果想进一步了解 RQ 的源码或者功能，可以使用 Zread，它已经索引了 RQ 的 GitHub 仓库，并且提供 AI 问答，目前免费： <a href="https://zread.ai/TanStack/query" target="_blank" rel="noopener noreferrer">https://zread.ai/TanStack/query</a></p>
</li>
</ol>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="扩展服务端组件中如何使用">扩展：服务端组件中如何使用？<a href="https://wukaipeng.com/en/blog/2025/11/05/2025/%20React%20Qeury#%E6%89%A9%E5%B1%95%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%BB%84%E4%BB%B6%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="Direct link to 扩展：服务端组件中如何使用？" title="Direct link to 扩展：服务端组件中如何使用？">​</a></h2>
<p>留一个问题</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[TypeScript 为什么要增加一个 satisfies？]]></title>
            <link>https://wukaipeng.com/en/blog/typescript-satisfies</link>
            <guid>https://wukaipeng.com/en/blog/typescript-satisfies</guid>
            <pubDate>Tue, 09 Sep 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[最近，在很多依赖库的类型定义文件中，经常能看到了一个陌生的朋友：satisfies。]]></description>
            <content:encoded><![CDATA[<p>最近，在很多依赖库的类型定义文件中，经常能看到了一个陌生的朋友：<code>satisfies</code>。</p>
<p>相信很多人都和我一样，看完 TypeScript 的相关文档，对这个关键字还是一头浆糊。</p>
<p><code>satisfies</code> 关键字是 TypeScript 4.9 版本引入的，用于<strong>类型断言</strong>。</p>
<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"><span 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">Connection</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><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">declare</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">createConnection</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  host</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  port</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  reconnect</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  poolSize</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></span><span class="token-line" style="color:#393A34"><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"> Connection</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div>
<p>这里，我们声明了一个函数 <code>createConnection</code>，它接收四个参数，返回一个 <code>Connection</code> 类型。</p>
<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"><span 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">Config</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  host</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  port</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 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></span><span class="token-line" style="color:#393A34"><span class="token plain">  tryReconnect</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 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 builtin">boolean</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  poolSize</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">number</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>我们又声明了一个 <code>Config</code> 类型，它包含了四个属性：<code>host</code>、<code>port</code>、<code>tryReconnect</code> 和 <code>poolSize</code>。</p>
<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"><span class="token-line" style="color:#393A34"><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"> Config </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></span><span class="token-line" style="color:#393A34"><span class="token plain">  host</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"localhost"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  port</span><span class="token operator" style="color:#393A34">:</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><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">tryReconnect</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 boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>我们声明了一个 <code>config</code> 变量，它包含这三个属性的值：<code>host</code>、<code>port</code>、<code>tryReconnect</code>。</p>
<p>OK，现在我们来调用 <code>createConnection</code> 函数，并传入 <code>config</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"><span 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">main</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></span><span 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"> host</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> port</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tryReconnect</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> poolSize </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"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> connection </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createConnection</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">host</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> port</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tryReconnect</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> poolSize</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>问题出现了：</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/09/09-201148-sgKZDN-image-20250909201147856.png" alt="port 类型错误" class="img_ev3q"></p>
<p>这里 <code>port</code> 的类型是 <code>string | number</code>，而 <code>createConnection</code> 函数的参数类型是 <code>string</code>，所以会报错。</p>
<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"><span 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">main</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> host</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> port</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tryReconnect</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> poolSize </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"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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"> port </span><span class="token operator" 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 punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    port </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> port</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">toString</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></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> connection </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createConnection</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">host</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> port</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tryReconnect</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> poolSize</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p><code>port</code> 类型正确了，但 <code>tryReconnect</code> 类型错误了：</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/09/09-202039-skL0mC-image-20250909202039012.png" alt="tryReconnect 类型错误" class="img_ev3q"></p>
<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"><span 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">main</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> host</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> port</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tryReconnect</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> poolSize </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"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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"> port </span><span class="token operator" 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 punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    port </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> port</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">toString</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></span><span 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></span><span 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"> tryReconnect </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></span><span class="token-line" style="color:#393A34"><span class="token plain">    tryReconnect </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">tryReconnect</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></span><span 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></span><span 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"> poolSize </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"undefined"</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    poolSize </span><span class="token operator" 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 plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> connection </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createConnection</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">host</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> port</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tryReconnect</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> poolSize</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p><code>port</code>、<code>tryReconnect</code>、<code>poolSize</code> 都进行了类型断言，问题解决了。</p>
<p>但是，这样写起来很麻烦，有没有更简单的方法呢？</p>
<p>一种方式是，去掉 <code>config</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"><span class="token-line" style="color:#393A34"><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"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  host</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"localhost"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  port</span><span class="token operator" style="color:#393A34">:</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><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">tryReconnect</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 boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></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"><span 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">main</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> host</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> port</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tryReconnect </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"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> connection </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createConnection</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">host</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> port</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">toString</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 function" style="color:#d73a49">tryReconnect</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 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>但这样放飞类型，会引起另外的错误，比如 <code>config</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"><span class="token-line" style="color:#393A34"><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"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  host</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"localhost"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  port</span><span class="token operator" style="color:#393A34">:</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><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">tryReconnect</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 boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  pool</span><span class="token operator" 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 plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 新增了一个属性</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>这样 TypeScript 是一点都不会报错，但却会埋下隐藏炸弹，在代码上线的时候，可能会抓马，<strong>为什么 <code>poorSize</code> 不生效？</strong></p>
<p>层层排查，最后才发现原来 <code>poolSize</code> 写错成了 <code>pool</code>。</p>
<p>这个时候，<code>satisfies</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"><span class="token-line" style="color:#393A34"><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"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  host</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"localhost"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  port</span><span class="token operator" style="color:#393A34">:</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><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">tryReconnect</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 boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  pool</span><span class="token operator" 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 plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> satisfies Config</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/09/09-203123-7CAJns-image-20250909203123363.png" alt="pool 类型错误" class="img_ev3q"></p>
<p>不负众望，TypeScript 终于报错，告诉我们 <code>pool</code> 属性不存在。</p>
<p><code>satisfies</code> 关键字为我们提供了一种两全其美的解决方案：</p>
<ol>
<li><strong>保证类型安全</strong>：它会检查我们的对象结构是否<strong>满足</strong>（satisfies）指定的类型（如 <code>Config</code>）。如果你写了多余的属性（如 <code>pool</code>），或者属性类型不匹配，TypeScript 会立刻报错。这避免了“放飞自我”带来的隐患。</li>
<li><strong>保留原始类型</strong>：与使用类型注解 (<code>: Config</code>) 不同，<code>satisfies</code> <strong>不会改变变量被推断出的原始具体类型</strong>。变量 <code>config</code> 的 <code>port</code> 属性类型仍然是 <code>number</code>，<code>tryReconnect</code> 属性类型仍然是 <code>() =&gt; boolean</code>。</li>
</ol>
<p>总结来说，<code>satisfies</code> 的核心优势在于：<strong>在不丢失（泛化）原始推断类型的前提下，对该值进行类型检查。</strong></p>
<p>这使得我们既能获得编辑器对于具体类型的智能提示和类型推断的好处，又能确保这个值的结构符合我们预先定义好的更宽泛的类型约束，从而写出更安全、更灵活的代码。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="references">REFERENCES<a href="https://wukaipeng.com/en/blog/typescript-satisfies#references" class="hash-link" aria-label="Direct link to REFERENCES" title="Direct link to REFERENCES">​</a></h2>
<ul>
<li><a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html" target="_blank" rel="noopener noreferrer">TypeScript 4.9</a></li>
<li><a href="https://www.youtube.com/watch?v=6uJeT7y6CCo" target="_blank" rel="noopener noreferrer">Satisfies Operator - new TypeScript 4.9 feature!</a></li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to open multiple Chrome instances at once?]]></title>
            <link>https://wukaipeng.com/en/blog/infinite-chrome</link>
            <guid>https://wukaipeng.com/en/blog/infinite-chrome</guid>
            <pubDate>Thu, 03 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Hi, I'm Kaipeng.]]></description>
            <content:encoded><![CDATA[<p>Hi, I'm Kaipeng.</p>
<p>Today I want to share a small Chrome tip: you can spin up multiple clean, isolated Chrome instances at once to make your development smoother.</p>
<p>Quick poll: which browsers do you usually use for development?</p>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Chrome</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Firefox</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Safari</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Other</li>
</ul>
<p>My daily driver for development is Chrome.</p>
<p>Chrome DevTools are powerful and cover the vast majority of frontend debugging needs.</p>
<p>But one thing has long bothered me: my everyday browsing and development are coupled together.</p>
<p>For example, I have lots of extensions installed:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/07/08-213533-tgVkG2-image-20250708213533037.png" alt="Chrome Extensions" class="img_ev3q"></p>
<p>These extensions can affect development because they may inject HTML or CSS into pages and generate extra network requests, which interferes with debugging.</p>
<p>For example, the sidebar HTML injected by an extension:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/08/06-000130-dQfKmL-image-20250806000129455.png" alt="Chrome Layer Tab" class="img_ev3q"></p>
<p>At this point, the usual choices are either opening an Incognito window or switching to another browser.</p>
<p>Both are fine, but Incognito still uses the same Chrome instance, and every time you reopen it, all state is cleared.</p>
<p>Switching to another browser is another option—I tried it but eventually gave up. It’s like moving to a brand-new dev environment: different UI, habits, shortcuts, etc. It feels awkward.</p>
<p>Recently I learned a better way: create separate Chrome instances by using different user data directories.</p>
<p>Run:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">/Applications/Google</span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"> Chrome.app/Contents/MacOS/Google</span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"> Chrome --user-data-dir</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"/tmp/chrome_user_dir_1"</span><br></span></code></pre></div></div>
<p>You’ll get a brand-new Chrome instance whose settings, extensions, history, etc. are all isolated.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/07/08-002759-3B2OHR-image-20250708002759144.png" alt="Create Chrome Instance" class="img_ev3q"></p>
<p>You’ll even see two Chrome icons in the Dock:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/07/08-003053-0GuO3E-image-20250708003053037.png" alt="Chrome Instances in Dock" class="img_ev3q"></p>
<p>This newly created instance is effectively a separate Chrome browser.</p>
<p>You can change its theme to distinguish it from other instances:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/07/08-210920-t5HzRo-image-20250708210919880.png" alt="Modify Theme" class="img_ev3q"></p>
<p>Or sign into a different account, etc.—it’s your second Chrome.</p>
<p>By running this command, you can theoretically create unlimited Chrome instances—just change the --user-data-dir parameter, for example:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">/Applications/Google</span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"> Chrome.app/Contents/MacOS/Google</span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"> Chrome --user-data-dir</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"/tmp/chrome_user_dir_2"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">/Applications/Google</span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"> Chrome.app/Contents/MacOS/Google</span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"> Chrome --user-data-dir</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"/tmp/chrome_user_dir_3"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">/Applications/Google</span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"> Chrome.app/Contents/MacOS/Google</span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"> Chrome --user-data-dir</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"/tmp/chrome_user_dir_4"</span><span class="token plain"></span><br></span><span 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></span></code></pre></div></div>
<p>In practice, I usually keep two instances and switch between them: one for casual browsing, one for development and debugging.</p>
<p>If launching a new Chrome instance every time during development feels a bit annoying, consider adding the command to your frontend project’s package.json scripts:</p>
<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"><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"scripts"</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"dev"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"next dev --turbopack"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"open-chrome"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --args --user-data-dir=/tmp/ChromeNewProfile http://localhost:3000"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"dev:chrome"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"npm run open-chrome &amp;&amp; npm run dev"</span><span class="token plain"></span><br></span><span 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></span></code></pre></div></div>
<p>Now you can run npm run dev<!-- -->:chrome<!-- --> to open the Chrome instance and automatically start next dev.</p>
<blockquote>
<p>Windows PowerShell users can use:</p>
<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"><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token property" style="color:#36acaa">"scripts"</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></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token property" style="color:#36acaa">"dev"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"next dev --turbopack"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token property" style="color:#36acaa">"open-chrome"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"powershell -Command \\\"Start-Process 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe' -ArgumentList '--user-data-dir=D:\\\\temp\\\\ChromeNewProfile', 'http://localhost:3000'\\\""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token property" style="color:#36acaa">"dev:chrome"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"npm run open-chrome &amp;&amp; npm run dev"</span><span class="token plain"></span><br></span><span 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></span></code></pre></div></div>
</blockquote>
<p>If you want the Chrome instance to open localhost:3000 automatically so you can see the page right away, append <a href="http://localhost:3000/" target="_blank" rel="noopener noreferrer">http://localhost:3000</a> to the command:</p>
<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"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"scripts"</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"dev"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"next dev"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"dev:chrome"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --user-data-dir=\\\"/tmp/chrome_user_dir_1\\\" http://localhost:3000 &amp;&amp; npm run dev"</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>That’s it for this post. If it helped, feel free to like, bookmark, and share.</p>
<p>I’m Kaipeng—see you next time.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Next.js Route Navigation Progress Bar: Enhancing User Experience with BProgress]]></title>
            <link>https://wukaipeng.com/en/blog/nextjs-router-progress</link>
            <guid>https://wukaipeng.com/en/blog/nextjs-router-progress</guid>
            <pubDate>Mon, 09 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to implement page transition progress bars in Next.js 15+ applications to solve user experience issues during route switching. Includes complete BProgress integration tutorial and code examples.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>This tutorial is also available as a video on YouTube 👉 <a href="https://youtu.be/ijz3apH8keY" target="_blank" rel="noopener noreferrer">【Next.js】Route Navigation Progress Bar</a></p>
</blockquote>
<p>Hello, I'm Kaypen.</p>
<p>Let's start with a bad example.</p>
<p>In Dify.ai, when you click to navigate to another page, there's a waiting period before the page actually transitions.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/06/22-001014-b22Y3p-dify.gif" alt="Dify.ai lacks progress feedback during page navigation" class="img_ev3q"></p>
<p>However, during this waiting time, I don't know whether the navigation was successful or not, so I end up clicking multiple times until the page finally changes.</p>
<p>This is a poor user experience 👎</p>
<p>The solution is simple. Let's look at how GitHub handles navigation interactions.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/06/22-001027-wIEPR6-github.gif" alt="GitHub's progress bar effect during page navigation" class="img_ev3q"></p>
<p>As you can see, GitHub displays a progress bar during navigation, clearly telling users - "I'm navigating, please wait."</p>
<p>So how can we implement this effect in Next.js?</p>
<p>We can achieve this using the <a href="https://bprogress.vercel.app/" target="_blank" rel="noopener noreferrer">BProgress</a> library.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/06/22-001139-FNp34B-iShot_2025-06-22_00.11.25.jpg" alt="BProgress official website homepage" class="img_ev3q"></p>
<p>BProgress is a lightweight progress bar component library that supports Next.js 15+, as well as other frameworks like Remix and Vue.</p>
<p>For using BProgress, I've created a demo project <a href="https://github.com/wukaipeng-dev/nextjs-progress-bar-demo" target="_blank" rel="noopener noreferrer">nextjs-progress-bar-demo</a>. Let's clone this project first:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">git</span><span class="token plain"> clone git@github.com:wukaipeng-dev/nextjs-progress-bar-demo.git</span><br></span></code></pre></div></div>
<p>Then enter the project directory:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token builtin class-name">cd</span><span class="token plain"> nextjs-progress-bar-demo</span><br></span></code></pre></div></div>
<p>First, install the dependencies:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">npm</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> @bprogress/next</span><br></span></code></pre></div></div>
<p>Start the project:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">npm</span><span class="token plain"> run dev</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/06/22-001402-BiLNbr-image-20250622001402486.png" alt="Next.js progress bar demo project interface" class="img_ev3q"></p>
<p>As you can see, this is a simple Next.js project with three pages: Home, Login, and Register.</p>
<p>The <code>main</code> branch already has the progress bar configured. Let's switch to the <code>without-progress-bar-demo</code> branch:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">git</span><span class="token plain"> checkout without-progress-bar-demo</span><br></span></code></pre></div></div>
<p>In this branch, we haven't configured the progress bar, so no progress bar will be displayed during page navigation.</p>
<p>Next, let's import <code>ProgressProvider</code> in the root layout <code>app/layout.tsx</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"><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">'use client'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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 string" style="color:#e3116c">"./globals.css"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-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"> </span><span class="token imports maybe-class-name">ProgressProvider</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">'@bprogress/next/app'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">RootLayout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  children</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><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"> </span><span class="token maybe-class-name">Readonly</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><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 punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></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><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span 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></span><span 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">html</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">lang</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">en</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></span><span 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">body</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line theme-code-block-highlighted-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">ProgressProvider</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">height</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">4px</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">color</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">#4c3aed</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">options</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"> showSpinner</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript boolean" style="color:#36acaa">false</span><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" style="color:#00009f"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">shallowRouting</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><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></span><span class="token-line theme-code-block-highlighted-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></span><span class="token-line theme-code-block-highlighted-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">ProgressProvider</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span 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">body</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span 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">html</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>Now, we can see that when navigating between the home page and login page, or between login and register pages, a progress bar will be displayed.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/06/22-001736-Lqx6xz-basic-integrate.gif" alt="Page navigation effect after integrating BProgress" class="img_ev3q"></p>
<p>The <code>ProgressProvider</code> parameters are:</p>
<ul>
<li><code>height</code>: The height of the progress bar</li>
<li><code>color</code>: The color of the progress bar</li>
<li><code>options</code>: Progress bar configuration. Here <code>showSpinner</code> is set to <code>false</code>, meaning no animated loading icon will be displayed.</li>
<li><code>shallowRouting</code>: Whether to enable shallow routing. If enabled, when only the route's query parameters change (e.g., <code>?page=1</code> to <code>?page=2</code>), the progress bar won't reload.</li>
</ul>
<p>However, after successful login, clicking to navigate won't show the progress bar.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/06/22-001924-LDl4LE-no-progress-when-login.gif" alt="Progress bar not showing when using router.push" class="img_ev3q"></p>
<p>This is because navigation between the home page and login page, or between login and register pages, uses the <code>&lt;Link&gt;</code> component.</p>
<p>The <code>&lt;Link&gt;</code> component actually renders as an <code>&lt;a&gt;</code> tag, and BProgress adds click events to all <code>&lt;a&gt;</code> components to show the progress bar.</p>
<p>We can check in DevTools → Elements → <code>&lt;a&gt;</code> → Event Listeners whether a click event has been added:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/06/22-002255-d9dYdk-iShot_2025-06-22_00.22.04.webp" alt="Viewing Link component's click event listeners in DevTools" class="img_ev3q"></p>
<p>But after successful login, we use <code>router.push</code> for navigation.</p>
<p>BProgress doesn't add click events to <code>router.push</code>, so naturally it won't show a progress bar.</p>
<p>Don't worry, BProgress provides us with a <code>useRouter</code> method.</p>
<p>Replace Next.js's <code>useRouter</code> with the <code>useRouter</code> provided by BProgress:</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"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// import { useRouter } from 'next/navigation';</span><span class="token plain"></span><br></span><span 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"> useRouter </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">'@bprogress/next/app'</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div>
<p>Then use it as normal:</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"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> router </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useRouter</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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">router</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">push</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><br></span></code></pre></div></div>
<p>Now you can see that after successful login, when automatically navigating to the home page, the progress bar displays correctly.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/06/22-002512-FZVedD-show-progress-when-login.gif" alt="Progress bar displays correctly after using BProgress's useRouter" class="img_ev3q"></p>
<p>But if your project has already wrapped its own <code>useRouter</code>, you can pass the wrapped <code>useRouter</code> as a parameter <code>customRouter</code> for a second wrapping:</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"><span 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"> useRouter </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">'@bprogress/next/app'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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"> useRouter </span><span class="token imports keyword module" style="color:#00009f">as</span><span class="token imports"> useNextIntlRouter </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">'@/i18n/navigation'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">Home</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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> router </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useRouter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    customRouter</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> useNextIntlRouter</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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></span><span 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">button</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><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 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><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">        router</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript method function property-access" style="color:#d73a49">push</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript string" style="color:#e3116c">'/about'</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><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" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">          startPosition</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript number" style="color:#36acaa">0.3</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></span><span class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">          locale</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">'en'</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></span><span 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 script language-javascript" style="color:#00009f"></span><br></span><span 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" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><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></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      Go to about page</span><br></span><span 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"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>Finally, let's go back to <code>app/layout.tsx</code>, where we imported <code>ProgressProvider</code> but turned <code>app/layout</code> into a client component. Let's extract <code>ProgressProvider</code> elsewhere and keep <code>app/layout</code> as a server component.</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"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// app/components/ProgressWrapper.tsx</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token string" style="color:#e3116c">'use client'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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"> </span><span class="token imports maybe-class-name">ProgressProvider</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">'@bprogress/next/app'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">ProgressWrapperProps</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><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 punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">ProgressWrapper</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 maybe-class-name">ProgressWrapperProps</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></span><span 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></span><span 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">ProgressProvider</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag attr-name" style="color:#00a4db">height</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">4px</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag attr-name" style="color:#00a4db">color</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">#0000ff</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag attr-name" style="color:#00a4db">options</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"> showSpinner</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript boolean" style="color:#36acaa">false</span><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" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag attr-name" style="color:#00a4db">shallowRouting</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><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></span><span 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></span><span 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">ProgressProvider</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>In <code>app/layout.tsx</code>, we import <code>ProgressWrapper</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"><span 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">ProgressWrapper</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">'./components/ProgressWrapper'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span 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">RootLayout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  children</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><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"> </span><span class="token maybe-class-name">Readonly</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><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 punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></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><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span 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></span><span 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">html</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">lang</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">en</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></span><span 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">body</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span 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">ProgressWrapper</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span 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></span><span 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">ProgressWrapper</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span 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">body</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span 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">html</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>Great job! You've completed the integration of a route navigation progress bar in Next.js.</p>
<p>That's all for this tutorial. I hope you found it helpful.</p>
<p>Thanks for reading! 👏</p>]]></content:encoded>
            <category>Next.js</category>
            <category>React</category>
            <category>Frontend Development</category>
            <category>User Experience</category>
            <category>Performance Optimization</category>
            <category>Tutorial</category>
        </item>
        <item>
            <title><![CDATA[Who Can Tell the Difference Between Next.js, Nest.js, and Nuxt.js?]]></title>
            <link>https://wukaipeng.com/en/blog/next-nest-nuxt</link>
            <guid>https://wukaipeng.com/en/blog/next-nest-nuxt</guid>
            <pubDate>Sat, 31 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[As a frontend developer, at least once in your career or studies, you'll encounter a situation where you need to distinguish between Next.js, Nest.js, and Nuxt.js.]]></description>
            <content:encoded><![CDATA[<p>As a frontend developer, at least once in your career or studies, you'll encounter a situation where you need to distinguish between <strong>Next.js</strong>, <strong>Nest.js</strong>, and <strong>Nuxt.js</strong>.</p>
<p>I just had one of these moments recently.</p>
<p>A new colleague joined our company, and during lunch chat, I heard him mention he had worked on <strong>Next.js</strong> projects before.</p>
<p>Since our company's new project is based on Next.js, I thought, "Great! Our new project is saved!"</p>
<p>But later in the group chat, he clarified that he had actually worked on <strong>Nest.js</strong> projects.</p>
<p>That really threw me off.</p>
<p>As an ordinary developer speaking ordinary Mandarin, it's completely normal not to be able to distinguish between the pronunciations of Next /nekst/ and Nest /nest/.</p>
<p>The authors who named these frameworks were really "clever."</p>
<p>These names are like wholesale products - all following the pattern N__t.js. The last time I was this confused was with the jewelry brands "Chow Tai Fook, Chow Luk Fook, Chow Sang Sang, Luk Fook Jewellery..."</p>
<p>This <strong>brand piggybacking</strong> actually peaked around 2016 when frontend frameworks were exploding.</p>
<p>That was a time when the frontend industry was blooming with various frameworks emerging constantly.</p>
<p>On October 25, 2016, Next.js 1.0 was released, making its debut as an open-source project.</p>
<p>Next.js is based on the React framework, providing Server-Side Rendering (SSR) and Static Site Generation (SSG) capabilities, along with features like automatic code splitting and routing systems.</p>
<p>Just one day later, on October 26, Nuxt.js was released.</p>
<p>I have to say, Nuxt.js copied really fast - it's based on Vue.js and created another version of Next.js.</p>
<p>Nest.js came later, releasing on February 26, 2017. It's quite different from Next.js and Nuxt.js.</p>
<p>It's a pure Node.js backend framework - essentially the JavaScript world's Spring Boot.</p>
<p>All three frameworks are doing well now. Besides their own technical merits, could their success be partly due to their <strong>"trend-riding"</strong> names?</p>
<p>Maybe next time when developing a new framework, we should just call it <strong>Not.js</strong>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Best Node.js Version Manager, Even Better Than NVM]]></title>
            <link>https://wukaipeng.com/en/blog/volta</link>
            <guid>https://wukaipeng.com/en/blog/volta</guid>
            <pubDate>Sat, 24 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Introduction to Volta]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="introduction-to-volta">Introduction to Volta<a href="https://wukaipeng.com/en/blog/volta#introduction-to-volta" class="hash-link" aria-label="Direct link to Introduction to Volta" title="Direct link to Introduction to Volta">​</a></h2>
<p>When we first started installing Node.js, we could only download the installation package from the official website to install a specific version, like 18.17.1.</p>
<p>But for different projects, we might need different Node.js versions, like 18.17.1 and 20.17.1.</p>
<p>To switch versions, you'd need to uninstall the old version, install the new one, then switch projects - extremely troublesome (pain mask).</p>
<p>So Node.js version managers emerged, like <a href="https://github.com/nvm-sh/nvm" target="_blank" rel="noopener noreferrer">NVM</a>, <a href="https://volta.sh/" target="_blank" rel="noopener noreferrer">Volta</a>, etc.</p>
<p>They support installing specific Node.js versions and allow free switching between versions.</p>
<p>However, NVM has some issues. For example, it can't automatically switch versions based on projects, doesn't support Windows platform (though there's an unofficial <a href="https://github.com/coreybutler/nvm-windows" target="_blank" rel="noopener noreferrer">nvm-windows</a> that works), etc.</p>
<p>The new generation Node.js version manager <a href="https://volta.sh/" target="_blank" rel="noopener noreferrer">Volta</a> solves these problems.</p>
<p>It can automatically switch Node.js versions based on projects and supports Mac, Windows, and Linux platforms.</p>
<p>Volta is built with Rust, making it faster and better.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="installing-volta">Installing Volta<a href="https://wukaipeng.com/en/blog/volta#installing-volta" class="hash-link" aria-label="Direct link to Installing Volta" title="Direct link to Installing Volta">​</a></h2>
<p>According to the <a href="https://docs.volta.sh/guide/getting-started" target="_blank" rel="noopener noreferrer">installation guide</a>, enter the following command in your terminal to install Volta:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-fsSL</span><span class="token plain"> https://get.volta.sh </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">bash</span><br></span></code></pre></div></div>
<p>After installation, open a new terminal and enter the following command to check the current Volta version:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">volta </span><span class="token parameter variable" style="color:#36acaa">-v</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain">.2</span><br></span></code></pre></div></div>
<p>Congratulations, Volta is successfully installed!</p>
<p>Now we can use Volta to manage Node.js versions.</p>
<p>Enter the following command in the terminal to install Node.js:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">volta </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">node</span><br></span></code></pre></div></div>
<p>This command will install the latest LTS version of Node.js.</p>
<blockquote>
<p>LTS: Long Term Support version.</p>
</blockquote>
<p>Of course, you can also use the at symbol <code>@</code> to install a specific Node.js version, for example:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">volta </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> node@20.17.1</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="project-level-nodejs-version-management">Project-Level Node.js Version Management<a href="https://wukaipeng.com/en/blog/volta#project-level-nodejs-version-management" class="hash-link" aria-label="Direct link to Project-Level Node.js Version Management" title="Direct link to Project-Level Node.js Version Management">​</a></h2>
<p>Open a Node.js project you're maintaining, like "shit-mountain", find the <code>package.json</code> file, and add the following content:</p>
<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"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"volta"</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></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"node"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"20.17.1"</span><span class="token plain"></span><br></span><span 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></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>When you run <code>npm i</code>, Volta will look for Node.js version 20.17.1.</p>
<p>If it can't find it, Volta will automatically install Node.js 20.17.1, then execute <code>npm i</code>.</p>
<p>This ensures the project uses Node.js version 20.17.1.</p>
<p>Volta has other features, like various Volta commands - <code>list</code>, <code>uninstall</code>, etc., as well as Hooks that can specify download sources. I won't elaborate here.</p>
<p>Visit the Volta website for more information 👉 <a href="https://volta.sh/" target="_blank" rel="noopener noreferrer">https://volta.sh</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Make Your Open Source Project More Attractive? Try Adding Vercel One-Click Deploy]]></title>
            <link>https://wukaipeng.com/en/blog/vercel</link>
            <guid>https://wukaipeng.com/en/blog/vercel</guid>
            <pubDate>Sun, 19 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[In some well-known GitHub open source projects, you'll find support for Vercel one-click deployment. For example, NextChat, which exploded in popularity two years ago and now has 78.7k stars:]]></description>
            <content:encoded><![CDATA[<p>In some well-known GitHub open source projects, you'll find support for Vercel one-click deployment. For example, <a href="https://github.com/ChatGPTNextWeb/NextChat?tab=readme-ov-file" target="_blank" rel="noopener noreferrer">NextChat</a>, which exploded in popularity two years ago and now has 78.7k stars:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2025/01/19-1737283120518-mLqxXh-3044a98f77c84a7191deec7fbe2f1b1d.png" alt="" class="img_ev3q"></p>
<p>So what is Vercel? It's a <strong>modern deployment platform</strong> designed specifically for frontend developers, particularly suitable for building, previewing, and deploying <strong>static websites and frontend applications</strong>.</p>
<p>So, if your open source project is a static website or frontend application, consider <strong>adding Vercel one-click deploy to your README.md to increase your project's attractiveness</strong>.</p>
<p>Adding one-click deployment is quite simple. Vercel provides a <strong>button generator tool</strong>: <a href="https://vercel.com/docs/deployments/deploy-button" target="_blank" rel="noopener noreferrer">deploy-button</a></p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2025/01/19-1737283120723-5ppjrX-702a4ec30d1c4f96b8ab8fb7f4e1e208.png" alt="" class="img_ev3q"></p>
<p>The button generator creates three formats: Markdown, HTML, and URL - you can choose what you need.</p>
<blockquote>
<p>Just a heads up, the interaction here might feel a bit odd. The form input is at the bottom of the page. For example, after filling in the Git repository URL, the Markdown link above will automatically change without any success notification. You'll need to get used to this.</p>
</blockquote>
<p>The only required field is your <strong>Git repository URL</strong>:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2025/01/19-1737283120951-cla5No-ea7f8ae650564f7489a2c7316fde2133.png" alt="" class="img_ev3q"></p>
<p>Other options include environment variables, default project name, redirects, demos, integrations, etc. <strong>Fill these as needed</strong>, then paste the generated Markdown into your open source project's README.md:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2025/01/19-1737283121127-iqyZZM-a3d1c64c282249e2a403c03a62a8b5e0.png" alt="" class="img_ev3q"></p>
<p><strong>That's the entire process</strong> - very simple!</p>
<p>From the user's perspective, when they click the deploy button, they'll be redirected to the Vercel website:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2025/01/19-1737283121487-w1u0qY-9af3c8b78217432ba5cd715a94ea89a4.png" alt="" class="img_ev3q"></p>
<p>Users need to log in to Vercel, and Vercel will request read/write permissions for Git repositories because Vercel needs to <strong>clone the target repository</strong> and deploy based on the cloned repository.</p>
<p>Fill in the project name and click create:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2025/01/19-1737283122589-LCgi3S-18fe758e918c4f3d9c7f7a112e9420bc.gif" alt="" class="img_ev3q"></p>
<p>Then just wait for completion:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2025/01/19-1737283123130-l91ujh-ac96dfa0cac34b5eacea153877655aa0.png" alt="" class="img_ev3q"></p>
<p><strong>Congratulations!</strong></p>
<p>You can already see the <strong>preview screenshot</strong> of the successfully running website. You can also click "Continue to Dashboard" to go to the control panel, then click the domain URL to see that the website has been successfully deployed:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2025/01/19-1737283124104-lbtiOe-2de354a20b8840b688a4a4b43aa758de.gif" alt="" class="img_ev3q"></p>
<p>Looking back at the whole process, Vercel's deployment service is incredibly smooth. I didn't even need to provide framework information, run commands, etc.</p>
<p>So, if you think Vercel one-click deployment is a good idea, consider adding it to your project!</p>
<blockquote>
<p>If needed, you can check out the example project from this article: <a href="https://github.com/wukaipeng-dev/ken" target="_blank" rel="noopener noreferrer">https://github.com/wukaipeng-dev/ken</a></p>
</blockquote>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[After a Year of Heavy Use, How I Pushed TickTick to Its Limits]]></title>
            <link>https://wukaipeng.com/en/blog/ticktick</link>
            <guid>https://wukaipeng.com/en/blog/ticktick</guid>
            <pubDate>Fri, 03 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Raycast's 2024 annual statistics are out. These stats include how many times I used Raycast to open other apps throughout the year. Seeing TickTick opened over a thousand times, ranking first, was a bit surprising.]]></description>
            <content:encoded><![CDATA[<p>Raycast's 2024 annual statistics are out. These stats include how many times I used Raycast to open other apps throughout the year. Seeing TickTick opened over a thousand times, ranking first, was a bit surprising.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012103-21Jwww-a198c020c5cc431487d87a5e9be925da.png" alt="" class="img_ev3q"></p>
<blockquote>
<p>TickTick's Chinese name: 滴答清单</p>
</blockquote>
<p>This year was my first year trying and gradually becoming a heavy user of TickTick. Initially, I just wanted to find a task management software. After comparing many other todo apps, TickTick's free version is what I personally consider the most generous - covering most features.</p>
<p>While the free aspect was tempting, it wasn't enough to win me over. What really convinced me was how it solved my long-standing pain points.</p>
<p>When working on tasks, I use the Pomodoro Technique, which means <strong>focusing for 25 minutes (1 Pomodoro), then taking a 5-minute short break, and after accumulating 4 Pomodoros, taking a long break</strong> - this cyclical rhythm for focus.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012103-MVPruL-a825b85f8da34d26a6b28a0cefc84bf2.png" alt="" class="img_ev3q"></p>
<p>Previously, I bought a desktop timer clock. Students preparing for exams should be familiar with this - something like this:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012103-60J4J2-614474f4bf4d47ce970fe3d4e5a41a01.png" alt="" class="img_ev3q"></p>
<p>This timer can count up or count down, perfect for a 25-minute Pomodoro countdown. I used this for over 2 years, buying one for home and another for the office.</p>
<p>It's convenient - physical buttons, one press to start a Pomodoro. But its advantage is also its disadvantage.</p>
<p>Physical limitations mean I can't carry a timer everywhere. More troublesome, at the end of the day, I don't know how many Pomodoros I spent, when I started them, or what tasks I worked on during each Pomodoro.</p>
<p>In TickTick, after adding a task, you can directly start a Pomodoro for that task, or associate tasks with ongoing Pomodoros, while adding notes. Opening the statistics page clearly shows the start and end of each Pomodoro.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012103-jeA8Bq-d3029f1122d54837accae40fb7ed9cf3.png" alt="" class="img_ev3q"></p>
<p>This basically solved my major pain point. Another minor itch was its notification feature. I often forget to start Pomodoros, but TickTick shows focus progress explicitly in the computer's menu bar:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012104-C5e9Ho-dabd490b317849558cc8b81fab433758.png" alt="" class="img_ev3q"></p>
<p>If cross-device sync is enabled, both phone and computer will sync current focus progress in real-time:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012104-jGOOxJ-1d6a47d0fdc041fcad73adf10533c0b3.png" alt="" class="img_ev3q"></p>
<p>What pleasantly surprised me was that after enabling focus mode on mobile, it automatically enters immersive mode. Besides the flip clock mode, there's this pixel mode with particularly beautiful UI:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012104-1Gw383-4f1ba5a5272245f9be23f0b066e53eee.png" alt="" class="img_ev3q"></p>
<p>After TickTick solved my Big Trouble, I started exploring other features.</p>
<p>For the core function - task management, the first point is task categorization. I believe many people's categories are quite messy, adding a "Reading" category today, a "Project Management" category tomorrow. Once categories multiply, they become hard to manage.</p>
<p>Here I recommend a very practical categorization method: <strong>categorizing by "roles"</strong></p>
<p>At work, the role is "Employee"; at school, it's "Student"; on the track, it's "Athlete". <strong>In any social relationship, everyone plays different roles</strong>.</p>
<p>In my personal TickTick categories, I have roles like Admin, Worker, Developer, Friend, Reader, etc.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012104-WHGC9A-d6f45fafa35f4a59a2d9ef6b48391097.png" alt="" class="img_ev3q"></p>
<p>This division method is very stable. Once my task categories were divided, there were almost no major changes. It's also flexible enough - for example, if you become a father, just add a "Father" role, and tasks like buying formula and changing diapers go under this role.</p>
<p>Categorizing by roles covers almost everything, but some roles need horizontal expansion. For instance, as an employee, you need to work on project a, project b, project c, etc. You can use tags to label tasks accordingly, distinguishing different task types under roles:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012104-AtwuWE-2658643c5770471888720e48bd94e54d.png" alt="" class="img_ev3q"></p>
<p>Through role division + tag system, you can basically establish an orderly and stable categorization system.</p>
<p>The second point is task processing. TickTick hides many thoughtful features, like setting the estimated number of Pomodoros for a task:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012104-eNOfae-82056f8929c44fa58dedfcf63623e7f2.png" alt="" class="img_ev3q"></p>
<p>Or setting task progress percentage:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012104-plT2n4-ef8dcaa2c9a648788ca761fdb77211c1.gif" alt="" class="img_ev3q"></p>
<p>After selecting multiple tasks by holding Shift or Command/Control, you can batch process them:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012104-wK5ELq-c20594b673354d5fb989e0904827b374.png" alt="" class="img_ev3q"></p>
<p>On mobile, long-press the app icon to add tasks. The task box has a voice-to-text function in the lower right corner to speed up task addition:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012105-6mYm9u-523e7d76cbbf4c79a1955511f5035110.png" alt="" class="img_ev3q"></p>
<p>Another feature is the calendar. I didn't have the habit of using it before, but recently discovered two points that made me realize calendars are amazing.</p>
<p>First discovery: you can use filter panels to view target tasks. Previously without filters, seeing all tasks piled together on the calendar gave me a headache:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012105-qZMuxy-e892015725d14094bdba7863ad29b7bd.png" alt="" class="img_ev3q"></p>
<p>Now using filters by list, tags, etc., you can easily view tasks corresponding to schedules:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012105-c7GTwR-66f3bee45055469d92393413e392dcea.png" alt="" class="img_ev3q"></p>
<p>Second discovery: calendars have a universal protocol - <a href="https://wukaipeng.com/weekly/9#7-calcav" target="_blank" rel="noopener noreferrer">CalDAV</a>, a protocol for calendar data sharing and synchronization, applicable to Android, iPhone, Windows, macOS, and all devices. Just need the calendar source to import and sync schedules anywhere.</p>
<p>In TickTick, by importing Feishu's CalDAV configuration, I can subscribe to all Feishu meetings and schedules:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012105-Br8Jel-f7c076cdea59469f942a50c135e2b770.png" alt="" class="img_ev3q"></p>
<p>There's also a habit function, for which I discovered three usage methods:</p>
<p>First is the most common positive habits:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012105-3f0itB-d472ba35970e4299bc68d7f89056b7d7.png" alt="" class="img_ev3q"></p>
<p>Second is bad habits:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012105-cjpwo2-45f856050b2d434ea42fddc07dddf927.png" alt="" class="img_ev3q">
This is opposite to positive habits - only recording when these bad habits occur. Scenarios for recording bad habits include:</p>
<ol>
<li>Recording low-frequency, occasional bad habits</li>
<li>When successfully cultivated as daily habits, no need for frequent recording, just record exceptions when not done</li>
</ol>
<p>Third is data recording</p>
<p>Habits have built-in calendars, which can be used for data recording. For example, during weight loss, you can record daily weight:
<img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012106-mLaqvx-ee237d0fe0bf48cfbb9f0ba37ab1f785.png" alt="" class="img_ev3q"></p>
<p>That's my personal use of TickTick over this year. It's indeed excellent software, but undeniably has limitations. For example, the Eisenhower Matrix merely divides tasks by importance and urgency in two dimensions:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012106-hvK80x-e17de740c7674b5a89c09ed2ee434e19.png" alt="" class="img_ev3q"></p>
<p>But no software in this world is perfect. When there are problems, solve them. Throughout 2024, I submitted nearly 30 bugs and feature requests to TickTick:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2025/01/03-012106-JP97Rb-0533529051ee418aa18a4a0664c6cd85.png" alt="" class="img_ev3q"></p>
<p>So, does this make me an unofficial tester + product manager? 😆</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[It's Almost 2025, I Won't Let You Not Know This AI Reading Tool]]></title>
            <link>https://wukaipeng.com/en/blog/baoyue</link>
            <guid>https://wukaipeng.com/en/blog/baoyue</guid>
            <pubDate>Wed, 23 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Recently, a friend asked me: what software can translate PDF documents into Chinese?]]></description>
            <content:encoded><![CDATA[<p>Recently, a friend asked me: what software can <strong>translate PDF documents into Chinese?</strong>
<img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213111-APsP4C-691b28252cc5478c8a7431fea61b73be.png" alt="" class="img_ev3q"></p>
<p>That stumped me for a moment. I usually only use simple Q&amp;A AIs like ChatGPT, Kimi, or Doubao. For <strong>actual PDF document translation scenarios, I hadn't encountered that</strong>.</p>
<p>In the end, my friend had to pay for it on Xiaohongshu, spending 50 yuan 💰</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213111-qpVGEq-25d645c1f0844d34976a31f0d7c2e5d3.png" alt="" class="img_ev3q"></p>
<p>Because I didn't know how to use AI, I actually missed a money-making opportunity!</p>
<p>Fortunately, I recently discovered an intelligent AI reading assistant called "<a href="https://baoyueai.com/home/scenes?utm_id=237648#part-8" target="_blank" rel="noopener noreferrer">Baoyue AI</a>".</p>
<p>Testing it with a PDF document, the translation results were quite impressive:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/24-232948-Kw6cKn-efc7945e49734c16966819d887fb5f0f-20241024232948019.png" alt="" class="img_ev3q"></p>
<p>Not only that, clicking to generate a mind map, a <strong>clearly structured mind map appears beautifully</strong>:
<img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213113-iQpOFe-a1cb61f958974c0eb97dea6fdaf079cd.png" alt="" class="img_ev3q"></p>
<p>Moreover, Baoyue AI also <strong>automatically generated a summary</strong>:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213113-2RP8dX-df6f52add5b84d1f99dd66598b9e0785.png" alt="" class="img_ev3q"></p>
<p>Besides PDF documents, it also supports word, ppt, epub, mobi, txt, markdown, jpg/png, webp, wps, caj, and even video and audio files.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213113-zoIJcZ-265fcd92a53b4467b5240fe4d430f85d.png" alt="" class="img_ev3q"></p>
<p>To help me read documents easily, Baoyue AI really went the extra mile.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/24-232900-9JPwZJ-57b8b607c27c450993c9263b8d253953-20241024232900498.png" alt="" class="img_ev3q"></p>
<p>Baoyue has a deep understanding of current AI large language model capabilities.</p>
<p>Current large models are actually <strong>not good at generating information</strong> - asking them to write articles just results in repetitive content going in circles.</p>
<p>But when it comes to deconstructing and understanding information, they excel.</p>
<p>Baoyue leverages AI's strengths, <strong>maximizing the advantages in deconstructing and understanding information</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213115-MOe0nu-c23edfb34c7d497c9ef1bafd2a36dcbc.png" alt="" class="img_ev3q"></p>
<p>In terms of specific product implementation, Baoyue does several things:</p>
<p>First, through easy-to-understand "reading scenarios" categorization, <strong>letting users quickly match their needs</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213115-79K2wp-198eb6b9ab8942f7aaa1ca3ddd5b97c2.png" alt="" class="img_ev3q"></p>
<p>Current scenarios include academic papers, contracts, office work, self-media, teaching assistant, books, official documents, technical R&amp;D, etc., <strong>meeting most requirements</strong>.</p>
<p>For example, recently while learning Coco's "CSS Technology Decryption and Practical Mastery", I used "Technical R&amp;D &gt; Line-by-line Code Explanation".</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/24-233010-xxu7qV-1f176d24d4444a3cbbf1bedf24e8a664-20241024233010292.png" alt="" class="img_ev3q"></p>
<p>Line-by-line explanation of all CSS code properties, learning efficiency Max 💪</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213117-icpIMr-e816b1caa3704a1ebc5758bf53db91dc.png" alt="" class="img_ev3q"></p>
<p>Second is rich information processing methods - it's not just a chatbot.</p>
<p>For example, using the "Self-media &gt; Tech News Deep Reading" scenario, I had Baoyue analyze this article <a href="https://wukaipeng.com/blog/zed" target="_blank" rel="noopener noreferrer">"Can Zed Beat VS Code?"</a></p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213117-fmrs1L-8f0b4378f2ec488aac6d396bbe0cdac0.png" alt="" class="img_ev3q"></p>
<p>Besides the mind map mentioned earlier, there's also an introduction to help users <strong>quickly understand the article's core points</strong>, as well as rewriting features for <strong>secondary creation</strong> and more.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213122-UmHy6W-c70ff7d6f447477b91457a3bf08323f1.png" alt="" class="img_ev3q"></p>
<p>Third is adding "AI Search" to let users <strong>connect to more related information</strong> - external enhancement.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213122-Kd0yQC-e6727b61351e444b9b6ee2da68ddb209.png" alt="" class="img_ev3q"></p>
<p>Fourth is having "note-taking capabilities", allowing users to <strong>record their divergent thoughts</strong> - internal enhancement.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/23-213123-399j7k-fdebbfac8cb442ebb2d98899c6fdd10e.png" alt="" class="img_ev3q"></p>
<p>Through this four-in-one approach, closing the entire loop from need identification, information processing, information expansion, to information digestion, <strong>perfectly solving reading scenarios</strong>.</p>
<p>I sincerely hope that domestic companies developing AI applications can be like Baoyue AI, <strong>truly focusing on and perfecting a vertical scenario</strong>, improving efficiency and saving time for users, creating real value.</p>
<p>👉 Baoyue AI official website: <a href="https://baoyueai.com/home/scenes?utm_id=237648#part-8" target="_blank" rel="noopener noreferrer">baoyueai.com</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Amazing Uses of Mac Notes App]]></title>
            <link>https://wukaipeng.com/en/blog/mac-notes</link>
            <guid>https://wukaipeng.com/en/blog/mac-notes</guid>
            <pubDate>Sun, 13 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Previously when using Windows, the most painful thing was not having an app that could satisfy my need for quick note-taking.]]></description>
            <content:encoded><![CDATA[<p>Previously when using Windows, the most painful thing was not having an app that could satisfy my need for <strong>quick note-taking</strong>.</p>
<p>Most apps required opening the note software first, then creating a new note, and finally typing - too many steps and too cumbersome.</p>
<p>After switching to macOS, what surprised me was the built-in Notes app. Just <strong>simply move your mouse to the bottom right corner of the screen</strong> to create a Quick Note.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232208-p1Fxnh-3f8e5b02ad934b37b03659c0452af268.gif" alt="" class="img_ev3q"></p>
<p>Amazing!</p>
<p>This method is called <strong>Hot Corners</strong>, which can be set in "System Settings » Desktop &amp; Dock » Hot Corners":
<img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232208-O6KaVV-1b54c6ffecfe4d7fa91477391c82dd6a.png" alt="" class="img_ev3q"></p>
<p>The four hot corners can be freely configured:
<img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232209-EJ0gJE-d6e28a4f9f134588b02a15f5306ba679.png" alt="" class="img_ev3q"></p>
<p>Besides hot corners, the keyboard shortcut <strong>fn(🌐) + Q</strong> can also create a Quick Note.</p>
<p>There's another issue - hot corners or shortcuts will open the last edited note by default. If you want to <strong>create a new Quick Note every time</strong>, you can change the setting here:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232209-ywHvRu-57fd2a786a00457986e8b2c68d784f7f.png" alt="" class="img_ev3q"></p>
<p>Uncheck "Always resume last Quick Note".</p>
<p>Notes supports <strong>most frequently used text styles</strong>. After selecting text, you can modify styles from the Aa button in the top navigation bar:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232209-dG8cIX-e8dfb79f7e4b43808e13150c484db1a4.png" alt="" class="img_ev3q"></p>
<p>It also supports checklists:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232209-djMwgQ-0497afc39df441a29dfea46c8fc712fa.png" alt="" class="img_ev3q"></p>
<p>The table function is quite basic - <strong>just a simple table</strong> without advanced features like merging or freezing.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232209-ojOhO8-4d8327cb2b06489a839cf3b07b4805b3.png" alt="" class="img_ev3q"></p>
<p>There are also images and links, which I won't elaborate on here.</p>
<p>Notes supports folder categorization by default, and also <strong>supports tag categorization</strong>. Just use the hashtag (#) followed by text in your note, and Mac will generate the corresponding tag list:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232210-i2Paen-f7f5af6816f14b6c97768bc4415f224e.png" alt="" class="img_ev3q"></p>
<p>Previously when browsing web pages, I really wanted to <strong>highlight certain content and make some extended notes</strong>. I installed the Weava Highlighter plugin, but it wasn't user-friendly - Weava would pop up whenever I selected text, which was very annoying.</p>
<p>Surprisingly, Mac Notes natively supports this feature!</p>
<p>In Safari, you can select content you want to save, right-click "Add to Quick Note":</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232210-4UsMyC-5b9541a4a8b94c2fad0e1a4c01230a88.png" alt="" class="img_ev3q"></p>
<p>After creating the Quick Note, the selected text will be highlighted in Safari:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232210-6umB4e-688c3d6114b743a5b72c2df1fb0bd54d.png" alt="" class="img_ev3q"></p>
<p>In the latest macOS 15 update, Notes now supports <strong>audio recording</strong>:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232210-EuzAws-e868ae71a6454655b7bf2afeeb3a3a0f.png" alt="" class="img_ev3q"></p>
<p>It also supports <strong>real-time speech-to-text</strong>, but currently it <strong>only supports English</strong> (again).</p>
<p>Cook's native language is English, mine is speechless 😅</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232210-7CyHWr-02148d8fbd15401c9dc5a7380edfc68c.gif" alt="" class="img_ev3q"></p>
<p>Additionally, there are new <strong>highlight colors</strong> - purple, pink, orange, mint, and blue. Have to say, these colors are quite nice looking.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232210-KejmGl-24abe463c0ce4b8891ddf687028cfb48.png" alt="" class="img_ev3q"></p>
<p>The most useful feature has to be the <strong>math function</strong>.</p>
<p>Just type formulas like <code>(27/3)^2=</code> or <code>47*96=</code>, and Notes will automatically calculate the result:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232210-TFwKhn-9d357ffa366f499a804a6ccfed0452f0.gif" alt="" class="img_ev3q"></p>
<p>It also supports custom variables:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/10/13-232211-fH3sw7-8969a94bba62451dae880010baad67f2.gif" alt="" class="img_ev3q"></p>
<p>Overall, Mac Notes is a decent note-taking app. While it lacks advanced features like Notion's document structure and block editing, it has native support and <strong>can meet the needs for quick note-taking and basic editing</strong>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Practical Interview Preparation Guide for Frontend Developers]]></title>
            <link>https://wukaipeng.com/en/blog/interview</link>
            <guid>https://wukaipeng.com/en/blog/interview</guid>
            <pubDate>Sat, 21 Sep 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Recently, there's a popular saying: "Good jobs are like HIV, only transmitted through mother-to-child, blood, and sexual contact"]]></description>
            <content:encoded><![CDATA[<p>Recently, there's a popular saying: "<strong>Good jobs are like HIV, only transmitted through mother-to-child, blood, and sexual contact</strong>"</p>
<p>Excluding HIV patients, for programmers from ordinary families, interviews are an inevitable path.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-122530-HWdvRQ-0XJ0X.gif" alt="" class="img_ev3q"></p>
<p>Generally speaking, interviews have four difficulties:</p>
<ol>
<li>Weak technical skills: Shaky foundation? Unfamiliar with algorithms?</li>
<li>Insufficient projects: No impressive projects? All failed projects?</li>
<li>Poor communication: Can't express yourself? No logic when speaking?</li>
<li>Mindset issues: Anxious about resumes being ignored? Depressed after failing first round? Stressed about resigning without a new job?</li>
</ol>
<p>These are actually just symptoms. The real root cause is:</p>
<p><strong>Lack of systematic understanding and preparation for interviews</strong></p>
<p>Next, I'll share my personal interview preparation approach from several aspects.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="1-have-a-clear-understanding-of-yourself">1. Have a Clear Understanding of Yourself<a href="https://wukaipeng.com/en/blog/interview#1-have-a-clear-understanding-of-yourself" class="hash-link" aria-label="Direct link to 1. Have a Clear Understanding of Yourself" title="Direct link to 1. Have a Clear Understanding of Yourself">​</a></h2>
<p>Many people have <strong>sky-high ambitions</strong> but often <strong>paper-thin fate</strong>.</p>
<p>Without clear self-awareness and planning, they enter the job market full of ambition, but quickly get slapped by reality.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-122247-vJ1Ycq-7XMnj.jpeg" alt="" class="img_ev3q"></p>
<p>At the start of interviews, you need to <strong>clarify your existing resources</strong>. For example, for me:</p>
<ul>
<li>Education: Non-prestigious bachelor's degree</li>
<li>Technical skills: Average foundation, no algorithm knowledge</li>
<li>Work experience: Never worked at a major tech company</li>
<li>Projects: No medium or large-scale project experience</li>
</ul>
<p>These are my disadvantages in interviews. So what are my advantages:</p>
<ul>
<li>Young and affordable, three years of work experience with some project experience, good value for companies</li>
<li>Some writing achievements, maintaining a personal technical blog</li>
<li>Open mindset, willing to learn and try new things</li>
</ul>
<p>After comprehensively analyzing my resources, my conclusion was:</p>
<ul>
<li>I have some market competitiveness, but <strong>haven't reached the threshold for major tech companies</strong> yet. Can try for mid-tier companies</li>
<li>Second-best outcome would be small companies or startups</li>
<li>Worst case would be subsidiaries of major companies, outsourcing companies, or Huawei OD</li>
</ul>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="2-prepare-well-for-a-protracted-battle">2. Prepare Well for a Protracted Battle<a href="https://wukaipeng.com/en/blog/interview#2-prepare-well-for-a-protracted-battle" class="hash-link" aria-label="Direct link to 2. Prepare Well for a Protracted Battle" title="Direct link to 2. Prepare Well for a Protracted Battle">​</a></h2>
<p>Interviews are like warfare, and it's a protracted battle.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="money">Money<a href="https://wukaipeng.com/en/blog/interview#money" class="hash-link" aria-label="Direct link to Money" title="Direct link to Money">​</a></h3>
<p>First is the financial aspect, especially if you resign without a new job - do you have enough savings to support yourself?</p>
<p>Don't expect to find a satisfactory job in one or two months - that's unrealistic.</p>
<p>Especially in the current environment, <strong>plan for at least 6+ months</strong> of savings.</p>
<p>Downgrade consumption where necessary, save where you can.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="mindset">Mindset<a href="https://wukaipeng.com/en/blog/interview#mindset" class="hash-link" aria-label="Direct link to Mindset" title="Direct link to Mindset">​</a></h3>
<p>Second is mindset. Most people start job hunting full of confidence.</p>
<p>But as time passes, with no responses to resumes, failed interviews, and concerned inquiries from family and friends, pressure builds.</p>
<p>It's easy to become anxious or even depressed. I've experienced this too.</p>
<p>My approach is to <strong>document</strong> it - <strong>accept your emotions and give yourself positive psychological reinforcement</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-120927-TfOVag-1726891755359.png" alt="" class="img_ev3q"></p>
<p>As Professor Luo Xiang once said: For uncontrollable things, we should remain optimistic; for controllable things, we should remain cautious.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="physical-strength">Physical Strength<a href="https://wukaipeng.com/en/blog/interview#physical-strength" class="hash-link" aria-label="Direct link to Physical Strength" title="Direct link to Physical Strength">​</a></h3>
<p>Finally, physical strength. Since job hunting is a protracted battle, you need sufficient physical stamina.</p>
<p>Staying at home browsing job sites, mass applying without results, constant rejections - under this double drain of mental and physical energy, people easily break down.</p>
<p>Better to go out for walks, breathe fresh air, do some exercise.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/22-090918-yWP83B-4mBWE.gif" alt="" class="img_ev3q"></p>
<p><strong>Maintaining healthy body and mind actually helps maintain confidence and improve job search efficiency</strong>.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="3-tools-for-efficiency">3. Tools for Efficiency<a href="https://wukaipeng.com/en/blog/interview#3-tools-for-efficiency" class="hash-link" aria-label="Direct link to 3. Tools for Efficiency" title="Direct link to 3. Tools for Efficiency">​</a></h2>
<p>During this preparation, I mainly used these tools:</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="flomo">Flomo<a href="https://wukaipeng.com/en/blog/interview#flomo" class="hash-link" aria-label="Direct link to Flomo" title="Direct link to Flomo">​</a></h3>
<p>Flomo is a "simple, efficient" <strong>fragment note-taking tool</strong> that helps you record, organize, and review your preparation process.</p>
<p>In Flomo, I mainly recorded my interview experiences, questions, feelings, reflections, etc.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-123545-ceocwI-image-20240921123545224.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="feishu">Feishu<a href="https://wukaipeng.com/en/blog/interview#feishu" class="hash-link" aria-label="Direct link to Feishu" title="Direct link to Feishu">​</a></h3>
<p>Feishu supports <strong>personal version</strong>, and I mainly used "Feishu Docs" and "Feishu Minutes" for preparation.</p>
<p>First, Feishu Docs is really great - rich document types, block documents like Notion, plus mind maps, Base tables, canvases, etc.</p>
<p>I created a <a href="https://v0oyefxd4a.feishu.cn/wiki/BmYzwLm1jidxJ8khdBicTgCpnlh" target="_blank" rel="noopener noreferrer">✨ Frontend Job Hunting Guide</a> with Feishu Docs, which I'll mention in the next section.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-125930-dllW6p-image-20240921125929824.png" alt="" class="img_ev3q"></p>
<p>For Feishu Minutes, you can upload audio MP3s and convert them to text - mainly used for <strong>post-interview self-review</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-130321-19B07V-image-20240921130321182.png" alt="" class="img_ev3q"></p>
<p>You can click on text to jump directly to the corresponding audio position - very convenient, with features like speed control and skip silence.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-130547-LJfCvs-image-20240921130547616.png" alt="" class="img_ev3q"></p>
<p>If you haven't used Feishu yet 👉 <a href="https://events.feishu.cn/7371707527425196033/recommend-form?i_code=9eI%2FDUQtwNN48zKqEo6BESIxiketv3hf&amp;reward_type=cash" target="_blank" rel="noopener noreferrer">Try Feishu</a></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="ticktick">TickTick<a href="https://wukaipeng.com/en/blog/interview#ticktick" class="hash-link" aria-label="Direct link to TickTick" title="Direct link to TickTick">​</a></h3>
<p>TickTick is a <strong>task management tool</strong>, mainly used for preparation planning.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-135638-8m3wbq-image-20240921135638493.png" alt="" class="img_ev3q"></p>
<p>TickTick also has a nice Pomodoro focus feature for tracking daily task progress and status.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-140157-wdmILx-image-20240921140059308.png" alt="" class="img_ev3q"></p>
<p>If you haven't used TickTick yet 👉 <a href="https://dida365.com/wechatInvite?c%3D3%26p%3D55y8ia7e%26t%3D0=" target="_blank" rel="noopener noreferrer">Try TickTick</a></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="ai-tools">AI Tools<a href="https://wukaipeng.com/en/blog/interview#ai-tools" class="hash-link" aria-label="Direct link to AI Tools" title="Direct link to AI Tools">​</a></h3>
<p>I have to admit, AI tools are really effective now.</p>
<p>I originally wanted to pay for guidance from seniors, but AI tools can achieve good results for free.</p>
<ul>
<li><strong>ChatGPT, Claude</strong>: Good answer quality, but requires VPN 🪜</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-140556-ruzcB9-image-20240921140555813.png" alt="" class="img_ev3q"></p>
<ul>
<li><strong>Doubao</strong>: ByteDance's AI Q&amp;A product. For non-technical questions, answer quality matches ChatGPT, supports PC client. My favorite feature is <strong>keyboard shortcuts</strong> like Spotlight - directly ask AI questions.</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-141009-83Wew3-image-20240921141009221.png" alt="" class="img_ev3q"></p>
<ul>
<li><strong>Tencent Yuanbao</strong>: Average answer quality, but can search WeChat articles before answering - after all, high-quality Chinese content is trapped in WeChat's closed platform.</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-141217-yt1QBd-image-20240921141217154.png" alt="" class="img_ev3q"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="4-create-your-personal-job-hunting-guide">4. Create Your Personal Job Hunting Guide<a href="https://wukaipeng.com/en/blog/interview#4-create-your-personal-job-hunting-guide" class="hash-link" aria-label="Direct link to 4. Create Your Personal Job Hunting Guide" title="Direct link to 4. Create Your Personal Job Hunting Guide">​</a></h2>
<p>I believe every job-seeking programmer should create a personal job hunting guide.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="why-create-a-job-hunting-guide">Why Create a Job Hunting Guide<a href="https://wukaipeng.com/en/blog/interview#why-create-a-job-hunting-guide" class="hash-link" aria-label="Direct link to Why Create a Job Hunting Guide" title="Direct link to Why Create a Job Hunting Guide">​</a></h3>
<p>The reason is simple - systematize the preparation process, <strong>consolidating</strong> preparation strategies, job search records, interview questions, and processes into <strong>one knowledge base</strong>.</p>
<p>Beyond this interview, your job hunting guide can be used for the next one - current efforts are <strong>reinforcing your future self</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/21-235633-47XUXi-8xpXZ.jpeg" alt="" class="img_ev3q"></p>
<p>I chose Feishu Docs because it's quite powerful and mind maps are free to use.</p>
<p>Of course, use whatever note-taking app you're familiar with - Yuque, Notion, Obsidian, even Word or Excel work.</p>
<p>Software tools are just means - what matters is your <strong>thinking and documentation</strong>.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="job-hunting-guide-reference-content">Job Hunting Guide Reference Content<a href="https://wukaipeng.com/en/blog/interview#job-hunting-guide-reference-content" class="hash-link" aria-label="Direct link to Job Hunting Guide Reference Content" title="Direct link to Job Hunting Guide Reference Content">​</a></h3>
<p>My Feishu document <a href="https://v0oyefxd4a.feishu.cn/wiki/BmYzwLm1jidxJ8khdBicTgCpnlh" target="_blank" rel="noopener noreferrer">✨ Frontend Job Hunting Guide</a> is now public - feel free to reference it.</p>
<p>First is the "Complete Guide" section, arranged by job search timeline, covering various aspects of job hunting.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/22-003207-rRIEzc-image-20240922003207872.png" alt="" class="img_ev3q"></p>
<p>If that's too much, just check the "Lite Quick Guide".</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/22-003351-4rxhs4-image-20240922003351583.png" alt="" class="img_ev3q"></p>
<p>Next is "Assignment 1: PDCJ" - for <strong>periodic review</strong> I use PDCJ, a thinking model:</p>
<ul>
<li>P is Plan</li>
<li>D is Do</li>
<li>C is Check</li>
<li>J is Just (adjust)</li>
</ul>
<p>Through periodic planning and implementation, then continuous checking and adjusting, ensure your interviews proceed <strong>correctly and efficiently</strong> without derailing.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/22-003549-0Y7SDN-image-20240922003549038.png" alt="" class="img_ev3q"></p>
<p>"Assignment 2: Interview Questions" is quite important. Beyond technical knowledge, deeply explore your own projects.</p>
<p>Not just successful projects - explore failed ones too, showing interviewers your <strong>deep, multi-angle thinking</strong> about projects.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/22-004025-jJtXw5-image-20240922004025124.png" alt="" class="img_ev3q"></p>
<p>Then "Assignment 3: Personalized Self-Introduction" - this is showing yourself.</p>
<p>In interviews, <strong>self-introduction is one of the most important parts</strong>, giving interviewers an initial impression of you.</p>
<blockquote>
<p>Though it depends on the interviewer - some may not like opening "self-introductions", preferring direct questions like "Tell me about your most fulfilling project".</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/22-081224-ICCDLF-image-20240922081224352.png" alt="" class="img_ev3q"></p>
<p>For "Assignment 4: Company Tracking Sheet", maintain all your application statuses including company name, position, application time, interview status, etc.</p>
<p>There are many job search channels for programmers now. This sheet helps maintain your job search status for <strong>coordination and timely follow-up</strong>.</p>
<p>As this data grows, you can <strong>analyze your application efficiency</strong> - for example, referral resume pass rates are much higher than Boss Zhipin applications, allowing you to adjust your job search strategy.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/22-091910-77Mu4E-image-20240922091910140.png" alt="" class="img_ev3q"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="5-some-final-tips">5. Some Final Tips<a href="https://wukaipeng.com/en/blog/interview#5-some-final-tips" class="hash-link" aria-label="Direct link to 5. Some Final Tips" title="Direct link to 5. Some Final Tips">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="1-dont-deny-yourself">1. Don't Deny Yourself<a href="https://wukaipeng.com/en/blog/interview#1-dont-deny-yourself" class="hash-link" aria-label="Direct link to 1. Don't Deny Yourself" title="Direct link to 1. Don't Deny Yourself">​</a></h3>
<p>Exams fail, relationships end, interviews bomb - these are all normal.</p>
<p><strong>Failure is the norm in life, success is rare</strong>.</p>
<p>Don't rush to deny yourself because of interview failures.</p>
<p>If you fail, review, reflect, summarize, and try again (PDCJ).</p>
<p>Here's a quote I really like:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/22-005535-rsZSuY-1.webp" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="2-be-humble">2. Be Humble<a href="https://wukaipeng.com/en/blog/interview#2-be-humble" class="hash-link" aria-label="Direct link to 2. Be Humble" title="Direct link to 2. Be Humble">​</a></h3>
<p>Schopenhauer said: <strong>Everyone takes the limits of their own vision as the limits of the world</strong>.</p>
<p>A frog at the bottom of a well thinks the world is limited to that well.</p>
<p>I've made this mistake before, thinking I was great, but the deeper I learn, the more I realize my insignificance and ignorance.</p>
<p>Now in interviews, I maintain a humble attitude.</p>
<p>When encountering my strong areas, explain eloquently, showing your abilities and thinking.</p>
<p>When encountering weak areas, honestly admit it and express willingness to learn and improve.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="3-about-outsourcing-companies">3. About Outsourcing Companies<a href="https://wukaipeng.com/en/blog/interview#3-about-outsourcing-companies" class="hash-link" aria-label="Direct link to 3. About Outsourcing Companies" title="Direct link to 3. About Outsourcing Companies">​</a></h3>
<p>For outsourcing companies - <strong>avoid if possible</strong>.</p>
<p>Compensation-wise, salaries and bonuses are reduced after the outsourcing company takes its cut.</p>
<p>Work content is often maintenance, secondary development, testing, and other miscellaneous tasks.</p>
<p>Technically, outsourcing companies often have poor technical culture, with colleagues likely lying flat.</p>
<p>Self-esteem wise, outsourced employees are often second-class citizens - internal network permissions, badges, meal cards all remind you of the gap with regular employees.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/22-102922-5Rg8AW-7a00b3fe7f86bd5f7710d974cd2ca02e.jpeg" alt="" class="img_ev3q"></p>
<p>So unless it's transitional (1-2 years), outsourcing companies aren't recommended.</p>
<p>Also, <strong>don't use outsourcing positions for interview practice</strong>.</p>
<p>Outsourcing interview questions, difficulty, and interviewer mindset differ from regular positions - little reference value.</p>
<p>Plus it requires significant time and energy - coordinating with HR, scheduling tests, interviews, commuting to on-site interviews, etc.</p>
<p>No need for this useless work - save energy for regular position interviews.</p>
<p>On Boss Zhipin, go to Settings &gt; Privacy &gt; Block Companies to block outsourcing companies and avoid interference.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com//2024/09/22-103515-BW9eXE-img_v3_02ev_da4b7316-beea-456a-b534-4c4593ed9a1g.jpg" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="4-use-everything-you-can">4. Use Everything You Can<a href="https://wukaipeng.com/en/blog/interview#4-use-everything-you-can" class="hash-link" aria-label="Direct link to 4. Use Everything You Can" title="Direct link to 4. Use Everything You Can">​</a></h3>
<p>I've heard that the entrepreneurial state is "<strong>courtesan mentality, widow treatment, women's federation pursuit</strong>".</p>
<p>This applies to job hunting too.</p>
<p>Your pursuit and goal is finding a satisfactory job, so lower your pride and use all available resources.</p>
<p>Contact your friend circles, classmate circles, former colleagues one by one - ask about referral opportunities.</p>
<p>Email experts to ask about suitable positions or interview advice.</p>
<p>Join tech groups, QQ groups, WeChat groups - communicate with experts and peers.</p>
<p>Expand your job search scope - don't limit yourself to Boss Zhipin, Lagou, Liepin, and other job sites.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[About to Leave My Job, Recording the Mac Apps I Use]]></title>
            <link>https://wukaipeng.com/en/blog/mac-application</link>
            <guid>https://wukaipeng.com/en/blog/mac-application</guid>
            <pubDate>Wed, 26 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[General Purpose]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="general-purpose">General Purpose<a href="https://wukaipeng.com/en/blog/mac-application#general-purpose" class="hash-link" aria-label="Direct link to General Purpose" title="Direct link to General Purpose">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="feishu">Feishu<a href="https://wukaipeng.com/en/blog/mac-application#feishu" class="hash-link" aria-label="Direct link to Feishu" title="Direct link to Feishu">​</a></h3>
<p>Believe it or not, the first recommendation is <strong>Feishu</strong>. It's the most comfortable project management application I've used.</p>
<p>Just <strong>Feishu Docs</strong> alone, its features and experience <strong>far exceed</strong> Tencent Docs, Shimo Docs, Yuque, and others on the market.</p>
<p>Now Feishu also supports <strong>personal version</strong>, no more thumbs up needed.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210639-8TvEm9-4627432cf1b94af3ac0ffa95d7a9a401.png" alt="" class="img_ev3q"></p>
<p>Besides documents, Feishu also supports email, video calls, and other features. Feishu = Enterprise WeChat + Tencent Docs + QQ Mail + Tencent Meeting, truly <strong>All in One</strong>.</p>
<p>Feishu also has some thoughtful small features, like <strong>OCR (image to text), video recording, GIF recording</strong>, and a very practical "<strong>scrolling screenshot</strong>" - no more worries about long screenshots.</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="arc">Arc<a href="https://wukaipeng.com/en/blog/mac-application#arc" class="hash-link" aria-label="Direct link to Arc" title="Direct link to Arc">​</a></h3>
<p>My main browser has switched from Chrome to <strong>Arc</strong>. Arc's <strong>multiple spaces</strong> solve my long-standing pain point.</p>
<p>It also automatically collapses expired tabs, <strong>supports full screen, looks beautiful, and has comfortable interactions</strong>. Arc is worth having.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210639-RGRaBZ-d4d0cfb965e740018694efa7ed1b6ce6.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="youdao-translate">Youdao Translate<a href="https://wukaipeng.com/en/blog/mac-application#youdao-translate" class="hash-link" aria-label="Direct link to Youdao Translate" title="Direct link to Youdao Translate">​</a></h3>
<p>For programmers reading official technical documentation, <strong>translation is a must</strong>.</p>
<p>I'm a long-time user of Youdao Translate, accompanying it from Youdao Dictionary to its current name <strong>Youdao Translate</strong>.</p>
<p>Although Youdao's translation quality isn't the best, it wins with its <strong>rich dictionary content and convenient hover translation</strong>.</p>
<p>Now with AI added, translation quality has improved significantly. Got myself a two-year membership.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210640-LjSWJi-aca5f1fa03b04fa5a8f6200ea64b3338.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="grammarly-desktop">Grammarly Desktop<a href="https://wukaipeng.com/en/blog/mac-application#grammarly-desktop" class="hash-link" aria-label="Direct link to Grammarly Desktop" title="Direct link to Grammarly Desktop">​</a></h3>
<p>When submitting issues or writing in English, worried about grammar mistakes? Grammarly Desktop comes in handy.</p>
<p>Basic <strong>grammar checking is free</strong>, but polishing and AI features require payment.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210640-giZcRy-a35a25744523409a8f817f51350968fc.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="ibar">iBar<a href="https://wukaipeng.com/en/blog/mac-application#ibar" class="hash-link" aria-label="Direct link to iBar" title="Direct link to iBar">​</a></h3>
<p>My Mac has a notch screen. When there are too many apps, they get hidden by the notch, which is annoying.</p>
<p>iBar can collapse the menu bar.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210640-EqfVIb-ffd48ba82c684ac099c9c927d4cb2cb1.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="ishot">iShot<a href="https://wukaipeng.com/en/blog/mac-application#ishot" class="hash-link" aria-label="Direct link to iShot" title="Direct link to iShot">​</a></h3>
<p>To capture beautiful screenshots <strong>with rounded corners and shadows</strong>, use iShot.</p>
<p>The free version is sufficient. Advanced paid features like long screenshots, OCR, and screen recording can be done with Feishu's built-in tools.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210640-bwh8DX-4b207245170e4b30b1312d65d041f39c.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="snipaste">Snipaste<a href="https://wukaipeng.com/en/blog/mac-application#snipaste" class="hash-link" aria-label="Direct link to Snipaste" title="Direct link to Snipaste">​</a></h3>
<p>When I switched from Windows to Mac, I continued using Snipaste. Its <strong>pin image feature</strong> is very useful.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210641-WOzqt5-0e9c54f649ed45559b226ed41f19ad1f.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="app-cleaner--uninstaller">App Cleaner &amp; Uninstaller<a href="https://wukaipeng.com/en/blog/mac-application#app-cleaner--uninstaller" class="hash-link" aria-label="Direct link to App Cleaner &amp; Uninstaller" title="Direct link to App Cleaner &amp; Uninstaller">​</a></h3>
<p>App Cleaner &amp; Uninstaller for <strong>thorough app uninstallation</strong>, clean and complete.
<img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210642-mFb9YY-608f905f7e994015985ea8481018d935.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="runcat">Runcat<a href="https://wukaipeng.com/en/blog/mac-application#runcat" class="hash-link" aria-label="Direct link to Runcat" title="Direct link to Runcat">​</a></h3>
<p>Watching <strong>a cat constantly running</strong> in the menu bar is also a form of entertainment.</p>
<p>Runcat can display <strong>current CPU usage</strong> in the menu bar. The more CPU is used, the faster the cat runs.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210642-lBB6Cj-5c9fc83b470642c7ad2eac6f4fb40540.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="activate-mac">Activate Mac<a href="https://wukaipeng.com/en/blog/mac-application#activate-mac" class="hash-link" aria-label="Direct link to Activate Mac" title="Direct link to Activate Mac">​</a></h3>
<p>A boring little widget, similar to Windows' "Activate Windows", displays "<strong>Activate macOS</strong>" in the bottom right corner of the screen.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210643-sZCRev-df65d35512f54bacaff50c97e76a55b0.png" alt="" class="img_ev3q"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="productivity">Productivity<a href="https://wukaipeng.com/en/blog/mac-application#productivity" class="hash-link" aria-label="Direct link to Productivity" title="Direct link to Productivity">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="personal-task-management-trio">Personal Task Management Trio<a href="https://wukaipeng.com/en/blog/mac-application#personal-task-management-trio" class="hash-link" aria-label="Direct link to Personal Task Management Trio" title="Direct link to Personal Task Management Trio">​</a></h3>
<ol>
<li>TickTick: Task planning and scheduled tasks</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210643-1us241-4413626f2afa4ee0bce2c02bf23dffea.png" alt="" class="img_ev3q"></p>
<ol start="2">
<li>Flomo: Recording inspiration, reviewing task processes or results</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210644-WxT4l6-56918f2910ce4913b91f46a1e127e6be.png" alt="" class="img_ev3q"></p>
<ol start="3">
<li>FlowUs: Review output, reading notes</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210644-OAz4yK-c340a23af14b4e95a8c46d33b734f22a.png" alt="" class="img_ev3q"></p>
<p>Why choose this toolkit?</p>
<ol>
<li>All are domestic applications, no VPN needed, avoiding anxious moments during network fluctuations</li>
<li>Cross-platform support: mobile, PC, web, mini-programs - full coverage</li>
<li>Good to use, replacing Todoist, Notion, etc.</li>
</ol>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="revezone">Revezone<a href="https://wukaipeng.com/en/blog/mac-application#revezone" class="hash-link" aria-label="Direct link to Revezone" title="Direct link to Revezone">​</a></h3>
<p>I often need to draw flowcharts and use Excalidraw for doodle-style drawings, but unfortunately it doesn't support Chinese fonts.</p>
<p>Revezone is like a localized version of Excalidraw, but goes further, integrating Tldraw, Notion-like note features, and providing a Mac client.</p>
<p>Currently open source and free, in alpha testing phase, worth trying.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210645-33gwzQ-785c32c8779942eba21b42b1fd306bf0.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="screen-recording">Screen Recording<a href="https://wukaipeng.com/en/blog/mac-application#screen-recording" class="hash-link" aria-label="Direct link to Screen Recording" title="Direct link to Screen Recording">​</a></h3>
<ol>
<li>OBS: <strong>Professional recording</strong>. Choose this for heavy-duty needs like course recording. For lightweight recording, use Feishu's GIF recording.</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210645-F0AGU9-94b49f51d76c4aa7a0c228fd458288bd.png" alt="" class="img_ev3q"></p>
<ol start="2">
<li>Screen Studio: Can create very smooth <strong>operation videos</strong></li>
</ol>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210646-QUbIXk-83a896837e8740aba7a4b06ecf4f7182.png" alt="" class="img_ev3q"></p>
<ol start="3">
<li>KeyCastr: <strong>Shows pressed keys</strong>, open source and free</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210646-UYmWqE-ca3f7055bb1b439f8fe261663ed3d8d0.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="utools">uTools<a href="https://wukaipeng.com/en/blog/mac-application#utools" class="hash-link" aria-label="Direct link to uTools" title="Direct link to uTools">​</a></h3>
<p>Truly a new generation efficiency tool platform. Everything is a plugin - whatever small feature you need can be found here.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210647-G0gD5l-4417c34efa954d548b48a0bba8c683ca.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="sunflower-remote-desktop">Sunflower Remote Desktop<a href="https://wukaipeng.com/en/blog/mac-application#sunflower-remote-desktop" class="hash-link" aria-label="Direct link to Sunflower Remote Desktop" title="Direct link to Sunflower Remote Desktop">​</a></h3>
<p>For remote computer access, I recommend Sunflower. The free version basically meets needs, though it pops up small ads.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210647-g9Ckd5-d412bf92bf4f495a97d021029f4a4d43.png" alt="" class="img_ev3q"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="development">Development<a href="https://wukaipeng.com/en/blog/mac-application#development" class="hash-link" aria-label="Direct link to Development" title="Direct link to Development">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="fork">Fork<a href="https://wukaipeng.com/en/blog/mac-application#fork" class="hash-link" aria-label="Direct link to Fork" title="Direct link to Fork">​</a></h3>
<p>A free <strong>Git visualization tool</strong>, aptly named "Fork".</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210648-4GzQpY-47e3688e9ce346d1a9d0bddb9a84118f.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="warp">Warp<a href="https://wukaipeng.com/en/blog/mac-application#warp" class="hash-link" aria-label="Direct link to Warp" title="Direct link to Warp">​</a></h3>
<p>A new terminal application supporting basic <strong>completion, memory</strong> features, plus <strong>AI assistance</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210649-E6kcQ2-7897db6aac2e4af38071913bd0ac10c1.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="zed">Zed<a href="https://wukaipeng.com/en/blog/mac-application#zed" class="hash-link" aria-label="Direct link to Zed" title="Direct link to Zed">​</a></h3>
<p>I won't recommend JetBrains, VSCode, etc. - these are already well-known code editors.</p>
<p>I'll recommend <strong>Zed</strong>, a new generation editor based on Rust with <strong>very fast startup and input speed</strong>.</p>
<p>Zed is just starting out, worth watching.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210650-AYMEK4-c085d2b1b9514e2082a6a4a7862859a1.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="database-visualization">Database Visualization<a href="https://wukaipeng.com/en/blog/mac-application#database-visualization" class="hash-link" aria-label="Direct link to Database Visualization" title="Direct link to Database Visualization">​</a></h3>
<p>The database visualization king Navicat is familiar to everyone. <strong>They finally released a free version called Navicat Premium Lite</strong>. As a lite version of Navicat Premium 17, it has all the basic features.</p>
<p>Officially stated: "It meets the basic database management needs of beginners and non-commercial users" (probably because there were too many pirated copies 🤣)</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210650-eBj8QB-8e84b1dcb4464d0d9c1c09ae5f615a20.png" alt="" class="img_ev3q"></p>
<p>If you only need lightweight database visualization, I recommend <strong>SQL Pro Studio</strong>.</p>
<p>While not as powerful as Navicat, it's a Mac <strong>native app with good performance</strong>. Basic operations (filtering, sorting) are covered, advanced features require writing SQL.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210650-WJR0oz-4709cbe11acf40b1b18d95ff94936b77.png" alt="" class="img_ev3q"></p>
<p>For Postgres databases, use Postico 2, <strong>specifically for Postgres</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210651-7N6ZB4-47f982fb43da490d8f0a194e5ec3131e.png" alt="" class="img_ev3q"></p>
<p>For Redis visualization, I recommend Another Redis Desktop Manager or Redis Insight. Each has pros and cons, can be used together.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210651-0hoGFm-5a83331e65144994912b174052053ddb.png" alt="" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210651-YzRXoz-0c776f59d9234e089863624098f78c40.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="typora--upic">Typora &amp; uPic<a href="https://wukaipeng.com/en/blog/mac-application#typora--upic" class="hash-link" aria-label="Direct link to Typora &amp; uPic" title="Direct link to Typora &amp; uPic">​</a></h3>
<p>When writing Markdown notes, Typora has always been my <strong>first love</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210652-zVksaN-e8ce678769dd46e08016711756b85e08.png" alt="" class="img_ev3q"></p>
<p>Typora doesn't support images natively, so I set up an image hosting service on my server, currently <strong>using uPic for image uploads</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210652-imvnSK-b55ee28c88db44c68f5ef9cf8301bda1.png" alt="" class="img_ev3q"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="he3">He3<a href="https://wukaipeng.com/en/blog/mac-application#he3" class="hash-link" aria-label="Direct link to He3" title="Direct link to He3">​</a></h3>
<p><strong>Developer toolbox</strong>, combining multiple development tools, plus some basic video editing, image cropping, audio conversion tools.
<img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/06/26-210653-SOsmMo-29c218b5803441bf8fc4281b39d8dbac.png" alt="" class="img_ev3q"></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Found a Google XSS Vulnerability]]></title>
            <link>https://wukaipeng.com/en/blog/google-xss-bug</link>
            <guid>https://wukaipeng.com/en/blog/google-xss-bug</guid>
            <pubDate>Sat, 11 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Programmer Matan found an XSS vulnerability and reported it to Google, receiving a reward of $3,133.7 (approximately ¥22,666 RMB).]]></description>
            <content:encoded><![CDATA[<p>Programmer Matan found an <a href="https://matan-h.com/common-google-xss" target="_blank" rel="noopener noreferrer">XSS vulnerability</a> and reported it to Google, receiving a reward of $3,133.7 (approximately <strong>¥22,666 RMB</strong>).</p>
<p>Here are Google Bug Hunter's reward rules:
<img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145858-1ntD9Q-693fe8f451cb41d09db69c3b6f7502d2.png" alt="" class="img_ev3q"></p>
<blockquote>
<p>👉 Image from <a href="https://bughunters.google.com/about/rules/google-friends/6625378258649088/google-and-alphabet-vulnerability-reward-program-vrp-rules" target="_blank" rel="noopener noreferrer">https://bughunters.google.com/about/rules/google-friends/6625378258649088/google-and-alphabet-vulnerability-reward-program-vrp-rules</a></p>
</blockquote>
<p>Here's how it started. Matan read an <a href="https://www.rcesecurity.com/2017/03/ok-google-give-me-all-your-internal-dns-information/" target="_blank" rel="noopener noreferrer">article exposing a Google SSRF vulnerability</a> that mentioned this Google website:</p>
<p>📍 <a href="https://toolbox.googleapps.com/" target="_blank" rel="noopener noreferrer">https://toolbox.googleapps.com</a></p>
<p>So he began exploring, first by <strong>checking the robots.txt file</strong>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">#apps-toolbox</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">User-Agent: *</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/browserinfo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/checkmx</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/dig</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/har_analyzer</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/loganalyzer</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/loggershark</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/messageheader</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/recovery</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/useragent</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/other_tools</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/encode_decode</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Allow: /apps/screen_recorder</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Disallow: *</span><br></span></code></pre></div></div>
<blockquote>
<p>robots.txt is a file at the website's root directory that tells web crawlers which pages can be crawled and which cannot, to <strong>avoid the website being overloaded with crawler requests</strong>.</p>
</blockquote>
<p>In the robots.txt file, each link corresponds to a tool webpage.</p>
<p>But there's one exception: <code>/apps/recovery</code> cannot be accessed directly.</p>
<p>After a simple search, he discovered it has subpages:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">recovery/domain_in_use</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">recovery/form</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">recovery/ownership</span><br></span></code></pre></div></div>
<p>These subpages can all accept multiple URL parameters, for example:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">recovery/domain_in_use?visit_id=xxx&amp;user=xxx&amp;domain=xxx&amp;email=xxx</span><br></span></code></pre></div></div>
<p>If you enter this <strong>continue redirect link</strong>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">https://toolbox.googleapps.com/apps/recovery/ownership?domain=example.com&amp;email=email@example.com&amp;case=45500368&amp;continue=/apps/recovery/...</span><br></span></code></pre></div></div>
<p>This link contains the parameter <code>domain=example.com</code>, and note there's also a <code>continue=/apps/recovery/...</code> parameter.</p>
<p>Entering this continue redirect link gives a prompt:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145858-ujXByq-ecb74bb86e2b4d8e993acc17f83a4067.png" alt="" class="img_ev3q"></p>
<p>Here's where the problem was discovered - the CONTINUE button's link actually comes from the <code>continue</code> parameter!</p>
<p>Matan tested it by injecting JavaScript code: <code>.../continue=javascript:alert(document.domain)</code></p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145858-vGGmIx-50e529c68ed04cdc93fdf4d140613673.png" alt="" class="img_ev3q"></p>
<p>Successfully executed ✅</p>
<p>This website has no CSP security policy and no protective measures, so it can fetch resources from any external source.</p>
<p>He continued trying to load an external malicious script that retrieves the user's IP address:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">.../continue=javascript:fetch(%27https://api.ipify.org?format=json%27).then(response=%3Eresponse.text()).then(data=%3E{alert(data);%20})</span><br></span></code></pre></div></div>
<p>Also successfully executed ✅</p>
<p>At this point, the XSS vulnerability was confirmed.</p>
<p>Let's review XSS knowledge:</p>
<p>XSS (Cross-Site Scripting), <strong>cross-site scripting attack</strong>, occurs when validation is not properly done, trusting user input and accepting malicious input from attackers (usually JavaScript code), causing that malicious input to execute on other users' pages. XSS is generally divided into three types:</p>
<ol>
<li><strong>Stored</strong>: Malicious input is permanently stored in the backend service. Whenever users open the website and retrieve that malicious input, the browser executes it.</li>
<li><strong>Reflected</strong>: Malicious input is embedded in URLs or other inputs and immediately forwarded by the backend. As long as users visit the crafted URL, the browser executes it.</li>
<li>DOM-based: Attackers manipulate the user's DOM structure to execute malicious code.</li>
</ol>
<p>Matan's example above is a typical reflected XSS.</p>
<p>But this XSS is so basic that even Matan himself found it hard to believe, especially since Google's technology is famously good in the industry.</p>
<p>However, Google habitually uses their own frameworks, and when these frameworks don't implement proper security policies, product failures are inevitable.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Can Zed Beat VS Code?]]></title>
            <link>https://wukaipeng.com/en/blog/zed</link>
            <guid>https://wukaipeng.com/en/blog/zed</guid>
            <pubDate>Fri, 12 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Let me start with the conclusion: No.]]></description>
            <content:encoded><![CDATA[<p>Let me start with the conclusion: <strong>No</strong>.</p>
<p>Zed, yet another new text/code editor.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-kwRP3O-7a5ca12c8ed94bb39d199acc4ff2e765.png" alt="" class="img_ev3q"></p>
<blockquote>
<p>👉 <a href="https://zed.dev/" target="_blank" rel="noopener noreferrer">https://zed.dev</a></p>
</blockquote>
<p>It officially went open source on January 24th this year. In less than three months, it has already reached 30k stars on GitHub.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-59Vms8-d22ed923492047229cef9433c8d1902c.png" alt="" class="img_ev3q"></p>
<p>As Zed's slogan says: "Code at the speed of thought".</p>
<p>From actual experience, Zed is indeed smoother than VS Code.</p>
<p>⬇️ Zed</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-38CbSu-ccdb750a0050401eaa214b24dec24d34.gif" alt="" class="img_ev3q"></p>
<p>⬇️ VS Code</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-Q779zH-0ad8f4f9d6f6498fbc9280108868383d.gif" alt="" class="img_ev3q"></p>
<p>The official website also provides typing performance comparison:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-BsiIPC-44c40715983f41649dd9716b0da58a2a.png" alt="" class="img_ev3q"></p>
<p>To input the letter <em>z</em> and display it on screen, Zed only needs 58 milliseconds, while VS Code needs 97 milliseconds.</p>
<p>Zed is 1.4 times faster than VS Code.</p>
<p>In terms of input performance, <strong>Zed wins</strong>.</p>
<p>The second core feature Zed promotes is <strong>multi-user collaborative programming</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-QHUkDM-88a9604967354cd0a734e67faf6ab6a3.gif" alt="" class="img_ev3q"></p>
<p>To be honest, I can't think of good practical use cases for this feature yet.</p>
<hr>
<p>So far, Zed is <strong>merely</strong> a decent text editor.</p>
<p>You could even say Zed <strong>hasn't made any substantial breakthroughs</strong> - it's a self-indulgent product.</p>
<p>Zed's advertised high performance doesn't represent a quantum leap, making it hard to impress users.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-bsdUOP-619bc0847b134e49b16258e3b67b077f.png" alt="" class="img_ev3q"></p>
<p>The gap between "58 milliseconds" and "97 milliseconds" isn't that significant.</p>
<p>Any actual developer knows that <strong>the bottleneck in programming isn't typing speed</strong>.</p>
<p>As for multi-user collaboration, this scenario currently seems unfriendly.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-Y7Qt5G-f8140739c59b46c3810e909ecc0259b3.gif" alt="" class="img_ev3q"></p>
<p>For document collaboration, domestic products like Feishu Docs and Tencent Docs are leaders that would crush Zed.</p>
<p>For code collaboration, Git is obviously the mainstream solution.</p>
<hr>
<p>Zed is too young. It hasn't even implemented basic markdown preview yet.</p>
<p>VS Code set the standard with its open-source, plugin-based ecosystem that attracted a massive following.</p>
<p>While Zed also has a plugin mechanism, <strong>how many contributors can we expect?</strong></p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-rnuMm7-985738d95568472a994ddcd32199c101.png" alt="" class="img_ev3q"></p>
<p>The book "Rework" mentions that <strong>people who fail in their first startup have the same probability of failing in their second</strong>.</p>
<p>Zed's team previously made the Atom editor, and now Atom exists in name only.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-APxr8r-ed69b1e3b82344eda91a2c1ed3411dcc.png" alt="" class="img_ev3q"></p>
<p>The team failed with Atom, and their comeback with Zed <strong>still isn't enough</strong>.</p>
<p>Zed will likely capture a portion of users, but won't become a domain success.</p>
<hr>
<p>How can Zed break through? The most important thing is to <strong>go with the flow</strong>.</p>
<p>Think about VS Code's timing: the prosperity of the internet drove open-source development, Eclipse was outdated, JetBrains was expensive, frontend specialization was emerging and urgently needed lightweight editors - these were all momentum for VS Code.</p>
<p>Currently, Zed's best momentum is obviously the <strong>AI direction</strong>.</p>
<p>But Zed clearly lacks sufficient support, only having Copilot code completion and Chat capabilities.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/05/11-145945-wS88cP-f70ae1454c564ef6a06864f835cdc720.gif" alt="" class="img_ev3q"></p>
<p>VS Code not only has these features but implements them more comprehensively.</p>
<p>The Zed team should think about what kind of editor they want to make that adapts to the current AI trend and creates new opportunities.</p>
<p>If they continue to be satisfied with insignificant performance improvements and vertical collaboration, continuing to compete in vertical tracks, then I wish them luck.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Guide to 100% Maximizing GitHub Copilot]]></title>
            <link>https://wukaipeng.com/en/blog/github-copilot</link>
            <guid>https://wukaipeng.com/en/blog/github-copilot</guid>
            <pubDate>Sun, 07 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Long before OpenAI launched ChatGPT, OpenAI had already partnered with GitHub to launch GitHub Copilot.]]></description>
            <content:encoded><![CDATA[<p>Long before OpenAI launched ChatGPT, OpenAI had already partnered with GitHub to launch GitHub Copilot.</p>
<blockquote>
<p>Hereinafter referred to as Copilot</p>
</blockquote>
<p>Copilot caused a sensation in the programming community at that time.</p>
<p>The radical claim back then was: <strong>Copilot will replace programmers</strong>.</p>
<p>Three years later, calm down - Copilot hasn't replaced programmers as hoped.</p>
<p>Instead, it's a powerful assistant that improves efficiency. Mastering it can <strong>multiply your programming efficiency</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-234638-MyWTBn-download.png" alt="" class="img_ev3q"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="copilot-pricing">Copilot Pricing<a href="https://wukaipeng.com/en/blog/github-copilot#copilot-pricing" class="hash-link" aria-label="Direct link to Copilot Pricing" title="Direct link to Copilot Pricing">​</a></h2>
<p>Domestic developers are relatively price-sensitive.</p>
<p>Copilot officially offers two subscription types: <strong>Individual</strong> and <strong>Team</strong>.</p>
<p>Let's skip the team version - if you have company support, you don't need to worry about this.</p>
<p>Individual pricing comes in two options:</p>
<ol>
<li>Monthly subscription, $10 per month, <strong>¥72.37 RMB</strong></li>
<li>Annual subscription, $100 per year, <strong>¥723.7 RMB</strong></li>
</ol>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-234839-d3CXp0-image-20240412234839346.png" alt="" class="img_ev3q"></p>
<p>The individual version includes a 30-day free trial period, so you can try it first.</p>
<p>GitHub is quite friendly - it <strong>supports domestic credit card payment</strong>, including China Merchants Bank, GuangFa Bank, China Construction Bank, etc.</p>
<p>For unofficial channels like Taobao or uTools plugins, they might be unstable but are cheaper.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="copilot-family-suite">Copilot Family Suite<a href="https://wukaipeng.com/en/blog/github-copilot#copilot-family-suite" class="hash-link" aria-label="Direct link to Copilot Family Suite" title="Direct link to Copilot Family Suite">​</a></h2>
<p>Using Copilot is simple - install the corresponding plugin in VSCode/JetBrains, then log in with your GitHub account.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-181521-InDV4W-12-180931-cqLDKX-image-20240412180930744.png" alt="" class="img_ev3q"></p>
<blockquote>
<p>👉 VSCode: <a href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot" target="_blank" rel="noopener noreferrer">https://marketplace.visualstudio.com/items?itemName=GitHub.copilot</a>
👉 JetBrains: <a href="https://plugins.jetbrains.com/plugin/17718-github-copilot" target="_blank" rel="noopener noreferrer">https://plugins.jetbrains.com/plugin/17718-github-copilot</a></p>
</blockquote>
<p>After installation, when typing code, Copilot will automatically suggest code. Press <code>Tab</code> to complete.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-181423-r0m99D-212964557-8d832278-61bb-4288-a8a7-47f35859e868.gif" alt="" class="img_ev3q"></p>
<p>JetBrains' Copilot plugin integration provides sidebar Chat functionality:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-182530-ZLDeBh-image-20240412182529852.png" alt="" class="img_ev3q"></p>
<p>For VSCode users, to enable sidebar Chat functionality, you need to install the additional Copilot Chat plugin:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-182139-JZOxxA-image-20240412182138873.png" alt="" class="img_ev3q"></p>
<p>After installation, the sidebar menu will have a new "Chat" option, with a chat interface like ChatGPT:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-182250-GevSkt-image-20240412182250704.png" alt="" class="img_ev3q"></p>
<p>VSCode is indeed a bit more troublesome, but has an advantage.</p>
<p>New Copilot features will be launched on VSCode first, like Copilot Voice.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-212938-WOOCt5-12-212913-8ez2BA-image-20240412212912912.png" alt="" class="img_ev3q"></p>
<p>Install this plugin in VSCode 👇 to experience <strong>voice programming</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-213107-dMajyo-image-20240412213107161.png" alt="" class="img_ev3q"></p>
<p>Although it currently only supports English, multilingual support will surely come soon.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-213300-UPF36A-63279c01-3941-46c5-bf51-284fbc31fbfe.gif" alt="Speech to text in Visual Studio Code Chat" class="img_ev3q"></p>
<p>Even with English-only support, I believe Chinese developers who've been learning English since childhood have excellent English skills.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-213812-4hp6KE-R.jpeg" alt="" class="img_ev3q"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="copilot-code-completion">Copilot Code Completion<a href="https://wukaipeng.com/en/blog/github-copilot#copilot-code-completion" class="hash-link" aria-label="Direct link to Copilot Code Completion" title="Direct link to Copilot Code Completion">​</a></h2>
<p>Besides Copilot's automatic triggering, we can also use <code>Option</code> + <code>\</code> to manually trigger code completion.</p>
<blockquote>
<p>Windows: <code>Alt</code> + <code>\</code></p>
</blockquote>
<p>Although the manual shortcut is rarely used, it's useful for testing Copilot during network fluctuations.</p>
<p>After triggering code completion, pressing <code>Tab</code> accepts all the code, but sometimes we only need partial completion.</p>
<p>You can use <code>Command</code> + → to accept completion code step by step:</p>
<blockquote>
<p>Windows: <code>Ctrl</code> + →</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-221048-rhh5pH-20240412221008_rec_-convert.gif" alt="" class="img_ev3q"></p>
<p>If you're not satisfied with the current completion, you can press <code>Option</code> + <code>]</code> or <code>Option</code> + <code>[</code> to switch to the next or previous completion:</p>
<blockquote>
<p>Windows: <code>Alt</code> + <code>]</code> or <code>Alt</code> + <code>[</code></p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-221510-ZAlVae-20240412221442_rec_-convert.gif" alt="" class="img_ev3q"></p>
<p>For VSCode, press <code>Command</code> + <code>I</code> to invoke Copilot Editor in code:</p>
<blockquote>
<p>Windows: <code>Ctrl</code> + <code>I</code></p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-222404-6ICwrS-20240412222305_rec_-convert.gif" alt="" class="img_ev3q"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="using-copilot-chat">Using Copilot Chat<a href="https://wukaipeng.com/en/blog/github-copilot#using-copilot-chat" class="hash-link" aria-label="Direct link to Using Copilot Chat" title="Direct link to Using Copilot Chat">​</a></h2>
<p>Copilot Chat is like having GPT-4 directly in your editor, allowing quick Q&amp;A about project issues:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-222909-NjtEzI-image-20240412222909416.png" alt="" class="img_ev3q"></p>
<p>For JetBrains IDEs, you need to right-click on files to show references:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-223012-8owLSw-image-20240412223012255.png" alt="" class="img_ev3q"></p>
<p>JetBrains IDEs are currently clumsy, requiring manual right-clicking to specify files and referencing entire files.</p>
<p>VSCode doesn't need explicit specification - it automatically determines whether to reference the entire file or just the selected portion:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-223257-kmNJYr-image-20240412223257582.png" alt="" class="img_ev3q"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="some-small-joys-with-copilot">Some Small Joys with Copilot<a href="https://wukaipeng.com/en/blog/github-copilot#some-small-joys-with-copilot" class="hash-link" aria-label="Direct link to Some Small Joys with Copilot" title="Direct link to Some Small Joys with Copilot">​</a></h2>
<p>Let me mention some of Copilot's nice-to-have features, like directly generating Commit Messages:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-223743-34Opnl-20240412223717_rec_-convert.gif" alt="" class="img_ev3q"></p>
<p>Generating Git Commits is really convenient - it detects all file changes and generates appropriate messages.</p>
<p>Another feature is variable renaming, though it's currently unstable, so I'll skip it for now.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="copilot-cli">Copilot CLI<a href="https://wukaipeng.com/en/blog/github-copilot#copilot-cli" class="hash-link" aria-label="Direct link to Copilot CLI" title="Direct link to Copilot CLI">​</a></h2>
<p>Copilot CLI is now fully available, allowing you to use Copilot in the command line.</p>
<p>For example, we can ask Copilot to explain what the <code>sudo apt-get</code> command means:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/12-225423-uOLDTd-image-20240412225423062.png" alt="" class="img_ev3q"></p>
<p>However, CLI is still quite dumb. It's better to use Warp's AI features, as mentioned in my previous article 👉</p>
<p><a href="https://mp.weixin.qq.com/s/l9IZeitz8zX7GjZFAR5xFg" target="_blank" rel="noopener noreferrer">Beautiful, Functional + Free AI-Powered Terminal Tool: Recommending Warp!</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Three Developers Supporting One Trillion Active Uses]]></title>
            <link>https://wukaipeng.com/en/blog/sqlite</link>
            <guid>https://wukaipeng.com/en/blog/sqlite</guid>
            <pubDate>Tue, 02 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[For many developers, SQLite is certainly familiar.]]></description>
            <content:encoded><![CDATA[<p>For many developers, SQLite is certainly familiar.</p>
<p>We know it's powerful, but didn't expect it to be THIS powerful.</p>
<p>SQLite currently has over one trillion (<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi>e</mi><mn>12</mn></mrow><annotation encoding="application/x-tex">1e12</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em"></span><span class="mord">1</span><span class="mord mathnormal">e</span><span class="mord">12</span></span></span></span>) active uses.</p>
<p>It's mainly used in:</p>
<table><thead><tr><th>Platform</th><th>Contains SQLite</th></tr></thead><tbody><tr><td>Mobile Devices</td><td>Every Android device, every iPhone and iOS device</td></tr><tr><td>Computers</td><td>Every Mac, every Windows 10 machine</td></tr><tr><td>Web Browsers</td><td>Every Firefox, Chrome, and Safari web browser</td></tr><tr><td>Communication Apps</td><td>Every Skype instance</td></tr><tr><td>Media Apps</td><td>Every iTunes instance, every Dropbox client</td></tr><tr><td>Financial Software</td><td>Every TurboTax and QuickBooks</td></tr><tr><td>Programming Languages</td><td>PHP and Python</td></tr><tr><td>Home Entertainment</td><td>Most televisions and set-top boxes</td></tr><tr><td>Automobiles</td><td>Most automotive multimedia systems</td></tr><tr><td>Others</td><td>Countless millions of other applications</td></tr></tbody></table>
<blockquote>
<p>👉 Table source: <a href="https://www.sqlite.org/mostdeployed.html" target="_blank" rel="noopener noreferrer">https://www.sqlite.org/mostdeployed.html</a></p>
</blockquote>
<p>And SQLite's entire development team? Just three people:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/21-091841-ZHGYCj-9cdd7881a86d467087b22b51435d8e47.png" alt="" class="img_ev3q"></p>
<blockquote>
<p>👉 Image source: <a href="https://www.sqlite.org/crew.html" target="_blank" rel="noopener noreferrer">https://www.sqlite.org/crew.html</a></p>
</blockquote>
<ul>
<li><em><strong>D. Richard Hipp</strong></em>: <strong>Started the SQLite project on May 29, 2000</strong> and continues as the project architect. Richard was born in, lives in, and works in Charlotte, North Carolina. He holds a Master's degree in Electrical Engineering from Georgia Tech (1984) and a Ph.D. from Duke University (1992), and is the founder of the consulting firm Hwaci.</li>
<li><em><strong>Dan Kennedy</strong></em>: An Australian currently living in Southeast Asia. He holds a degree in Computer Systems Engineering from the University of Queensland and has worked in various fields including industrial automation, computer graphics, and embedded software development. <strong>Dan has been a major contributor to SQLite since 2002.</strong></li>
<li><em><strong>Joe Mistachkin</strong></em> (pronounced "miss-tash-kin"): A software engineer and one of the maintainers of Tcl/Tk. He is also the author of the TclBridge component and the Eagle scripting language. He has been in the software industry since 1994.</li>
</ul>
<p>Another interesting fact is that SQLite doesn't accept any external code contributions.</p>
<p>In other words, SQLite is open source but not open to code contributions.</p>
<p>This is mentioned in SQLite's copyright statement:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/04/21-091841-PzdJPG-550640d42a8244cd81e7f3887937414e.png" alt="" class="img_ev3q"></p>
<blockquote>
<p>👉 Image source: <a href="https://www.sqlite.org/copyright.html" target="_blank" rel="noopener noreferrer">https://www.sqlite.org/copyright.html</a></p>
</blockquote>
<p>Many times, we can't help but marvel at the <strong>marginal cost of software</strong>.</p>
<p>One piece of code can be distributed to ten people, or to <strong>one billion people</strong>.</p>
<p>Three developers supporting one trillion active uses.</p>
<p>The value created by SQLite is unparalleled. Technology changes the world.</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_WYt5" id="references">REFERENCES<a href="https://wukaipeng.com/en/blog/sqlite#references" class="hash-link" aria-label="Direct link to REFERENCES" title="Direct link to REFERENCES">​</a></h2>
<ul>
<li><a href="https://x.com/iavins/status/1774464622067679738" target="_blank" rel="noopener noreferrer">https://x.com/iavins/status/1774464622067679738</a></li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Beautiful, Functional + Free AI-Powered Terminal Tool: Recommending Warp]]></title>
            <link>https://wukaipeng.com/en/blog/warp</link>
            <guid>https://wukaipeng.com/en/blog/warp</guid>
            <pubDate>Thu, 14 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Recently, Warp has gained quite a reputation, with many people recommending this terminal tool, so I decided to download and try it out.]]></description>
            <content:encoded><![CDATA[<p>Recently, Warp has gained quite a reputation, with many people recommending this terminal tool, so I decided to download and try it out.</p>
<p>After several days of in-depth experience, the conclusion is that <strong>it can indeed replace my previous iTerm</strong>.</p>
<p>Warp has good aesthetics, looks comfortable, and satisfies the fancy requirements of frontend engineers.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/03/19-075844-wDr058-6a2ea263bd1a46a380f28df4259ebeff.png" alt="" class="img_ev3q"></p>
<p>There are <strong>multiple themes</strong> to choose from:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/03/19-075844-2PfILQ-a9569bc5082343adbf4e4e847730d3cc.png" alt="" class="img_ev3q">
<img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/03/19-075844-fL9KDD-0c8d9d9ed8764500bb8a75092a40e76a.png" alt="" class="img_ev3q"></p>
<p>Warp directly provides <strong>command prompt</strong> functionality, without the hassle required with iTerm:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/03/19-075844-3qOEJB-b79760d2485446dfb7becf190ab2754b.png" alt="" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/03/19-075844-IbGPZP-fdf4f542aacf41efb559a4b27187c13a.png" alt="" class="img_ev3q"></p>
<p>The best feature, of course, is the AI capability. You can open "<strong>Warp AI</strong>" in the upper right corner. If you forget a command, you can chat directly:
<img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/03/19-075844-UI5xRL-8f8b548ac1e24360bd0fdb3b97ae9f60.png" alt="" class="img_ev3q"></p>
<p>For Free users, you get <strong>20 free queries per day</strong>, which is sufficient for personal use:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/03/19-075844-cN4fiP-ad732ea288ed44c284c2c9e9f647a0b6.png" alt="" class="img_ev3q"></p>
<p>Warp also provides "<strong>Warp Drive</strong>", which allows you to <strong>save and share</strong> commands within your team:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/03/19-075844-ZY4wRr-05392aaaecde4ad7883935acc81ba2d1.png" alt="" class="img_ev3q"></p>
<p>Currently, Warp <strong>only supports Mac and Linux</strong>, but Windows support is coming soon:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/03/19-075845-X9ZqN9-0fb29ce2b4954f66915ada8a7fb972c2.png" alt="" class="img_ev3q"></p>
<p>If you think Warp looks good, give it a try:
👉 URL: <a href="https://app.warp.dev/referral/Q9ZVGQ" target="_blank" rel="noopener noreferrer">https://app.warp.dev/referral/Q9ZVGQ</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Run Windows on Mac Without Spending a Penny (M1/M2 Version)]]></title>
            <link>https://wukaipeng.com/en/blog/utm-fusion</link>
            <guid>https://wukaipeng.com/en/blog/utm-fusion</guid>
            <pubDate>Thu, 15 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Here's the effect of experiencing the latest Windows 11 on MacOS M1:]]></description>
            <content:encoded><![CDATA[<p>Here's the effect of experiencing the latest Windows 11 on MacOS M1:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/02/16-141107-GcAOad-image-20240216112510958.png" alt="" class="img_ev3q"></p>
<p><strong>VMware Fusion</strong> can run Windows and Linux systems, with a free license for personal use.</p>
<p>Installation guide here 👉 <a href="https://zhuanlan.zhihu.com/p/452412091" target="_blank" rel="noopener noreferrer">https://zhuanlan.zhihu.com/p/452412091</a></p>
<p>From applying for a Fusion license to downloading images, to installation pitfalls.</p>
<p>This article explains everything in great detail.</p>
<hr>
<p>Fusion has excellent support for Windows 11, but doesn't support other versions.</p>
<p>Here I recommend <strong>UTM</strong>. This is UTM running Windows 7:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/02/16-141110-fQTSMS-image-20240216113429868.png" alt="" class="img_ev3q"></p>
<p>UTM is also a free virtual system software.</p>
<p>Official website 👉 <a href="https://mac.getutm.app/" target="_blank" rel="noopener noreferrer">https://mac.getutm.app</a></p>
<blockquote>
<p>Note: UTM can be downloaded directly from the official website</p>
<p>But downloading from the App Store requires payment. If you want to support UTM, you can choose to pay for the download.</p>
</blockquote>
<br>
<p>UTM installation is quite simple.</p>
<p>First download the desired image. Images can be downloaded from MSDN 👉 <a href="https://msdn.itellyou.cn/" target="_blank" rel="noopener noreferrer">https://msdn.itellyou.cn</a></p>
<p>Then download the corresponding configuration file from UTM <a href="https://mac.getutm.app/gallery/" target="_blank" rel="noopener noreferrer">https://mac.getutm.app/gallery/</a></p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/02/16-114203-b8kslZ-image-20240216114203621.png" alt="" class="img_ev3q"></p>
<p>After downloading, you'll get a file with .utm extension, then open that file.</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/02/16-114350-0BIAqS-image-20240216114350208.png" alt="" class="img_ev3q"></p>
<p>After opening, you need to change the system image path.</p>
<p>In the upper right corner, change it to the corresponding downloaded system image:</p>
<p><img decoding="async" loading="lazy" src="https://img.wukaipeng.com/2024/02/16-114624-j5OEoX-image-20240216114512243.png" alt="" class="img_ev3q"></p>
<p>After changing, restart and you're done!</p>]]></content:encoded>
        </item>
    </channel>
</rss>