<?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>Musher's Frontend Coding Blog</title>
        <link>/</link>
        <description>無限進步</description>
        <lastBuildDate>Mon, 22 Jul 2024 02:24:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-CN</language>
        <copyright>All rights reserved 2024, Musher</copyright>
        <item>
            <title><![CDATA[Promptopia 开发笔记（3）——实现一个自定义Hook]]></title>
            <link>/Promptopia-dev-note-implement-custom-hooks</link>
            <guid>/Promptopia-dev-note-implement-custom-hooks</guid>
            <pubDate>Fri, 12 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[从实践经验出发，聊聊如何实现一个自定义hooks]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-aad73222ad1a482fa6bb0e9967ecc75f"><div class="notion-viewport"></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-aeb3fb9a2fc54b4bb5b0ce0481ff7328" data-id="aeb3fb9a2fc54b4bb5b0ce0481ff7328"><span><div id="aeb3fb9a2fc54b4bb5b0ce0481ff7328" class="notion-header-anchor"></div><a class="notion-hash-link" href="#aeb3fb9a2fc54b4bb5b0ce0481ff7328" title="前言"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">前言</span></span></h3><div class="notion-text notion-block-b52be16a3ab74c56bd8b95fff6e04247">在聊如何实现一个自定义Hook之前，我们先聊聊什么是Hook，什么是自定义Hook，以及我们什么时候需要自定义Hook？</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-625642718ac54db196dbfaedd0fb29f3" data-id="625642718ac54db196dbfaedd0fb29f3"><span><div id="625642718ac54db196dbfaedd0fb29f3" class="notion-header-anchor"></div><a class="notion-hash-link" href="#625642718ac54db196dbfaedd0fb29f3" title="什么是 Hook？"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">什么是 Hook？</span></span></h4><div class="notion-text notion-block-7a27bc173e504ec3a0291daec36f6276">在React中，常见的Hook主要包括<code class="notion-inline-code">useState</code> 、<code class="notion-inline-code">useEffect</code> 、<code class="notion-inline-code">useContext</code> 以及<code class="notion-inline-code">useRef</code> ，其中，<code class="notion-inline-code">useState</code>和<code class="notion-inline-code">useRef</code>主要用于提供响应式变量，<code class="notion-inline-code">useEffect</code>主要用于侦听变量做出响应式的变化，<code class="notion-inline-code">useContext</code>用于提供统一的上下文，综上我们可以得出，Hook主要用于在React中管理<b>状态和副作用。</b></div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-cb814966e234464eb42197acacfa3500" data-id="cb814966e234464eb42197acacfa3500"><span><div id="cb814966e234464eb42197acacfa3500" class="notion-header-anchor"></div><a class="notion-hash-link" href="#cb814966e234464eb42197acacfa3500" title="什么是自定义Hook？"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">什么是自定义Hook？</span></span></h4><div class="notion-text notion-block-8d23d8e08db34877bf2524eb48535526">从结果上来看，只要我们涉及状态和副作用管理，并且需要调用以上的这些原生Hook，这样我们封装出来的一个函数就是一个自定义Hook</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-5e86e833dae747e29f136ae0c7f3d173" data-id="5e86e833dae747e29f136ae0c7f3d173"><span><div id="5e86e833dae747e29f136ae0c7f3d173" class="notion-header-anchor"></div><a class="notion-hash-link" href="#5e86e833dae747e29f136ae0c7f3d173" title="我们为什么需要自定义Hook？"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">我们为什么需要自定义Hook？</span></span></h4><div class="notion-text notion-block-c45dc5e368d9477d90329b56e76f8dbc">就像我们经常需要将一些常用的功能封装成工具类一样，一些设计状态和副作用管理的逻辑，我们并不希望将其与UI部分混在一起，而是抽象出来，以便将逻辑和UI分离，并且方便我们复用。</div><hr class="notion-hr notion-block-4a131e793df14c3295d500378930070b"/><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-e2c91ee4c10a48cf99d2c122218a4946" data-id="e2c91ee4c10a48cf99d2c122218a4946"><span><div id="e2c91ee4c10a48cf99d2c122218a4946" class="notion-header-anchor"></div><a class="notion-hash-link" href="#e2c91ee4c10a48cf99d2c122218a4946" title="如何实现一个自定义Hook？"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">如何实现一个自定义Hook？</span></span></h3><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-ff8ddcac6a0e4ad48939b45049996072" data-id="ff8ddcac6a0e4ad48939b45049996072"><span><div id="ff8ddcac6a0e4ad48939b45049996072" class="notion-header-anchor"></div><a class="notion-hash-link" href="#ff8ddcac6a0e4ad48939b45049996072" title="需求"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">需求</span></span></h4><ol start="1" class="notion-list notion-list-numbered notion-block-a17fea9b5cde4c10902ff3d4e8d114b8"><li>在信息流页面实现一个搜索框，这个页面里有一个输入框，下面是一个用于展示信息流的组件；</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-b4336ec7d8354c779defdcf74a0388b3"><li>你需要实现基于用户名、内容以及tag的搜索；</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-d2fce83b42fd4203bda97bb53d66ce7b"><li>信息流中有可以点击的tag，tag点击后的行为需要反应到输入框中，进行搜索；</li></ol><ol start="4" class="notion-list notion-list-numbered notion-block-9c3f1343f6794820a0f7170958e8bace"><li>搜索行为对输入具有响应性，用户在完成输入后不需要手动敲击回车，而是会自动进行搜索。</li></ol><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-e8a98d7dc47c4f58af841fe1e8fd1e9e" data-id="e8a98d7dc47c4f58af841fe1e8fd1e9e"><span><div id="e8a98d7dc47c4f58af841fe1e8fd1e9e" class="notion-header-anchor"></div><a class="notion-hash-link" href="#e8a98d7dc47c4f58af841fe1e8fd1e9e" title="分析需求"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">分析需求</span></span></h4><div class="notion-text notion-block-04f0d3d779ea4309907ebf0f00f50316">实现一个自定义Hook，我们首先需要考虑，当前的页面需要什么数据和功能？这决定了我们在自定义Hook中需要对外暴露哪些state和setState，以及handler，搞清楚需要什么，写起代码来自然得心应手。</div><div class="notion-text notion-block-619e945df157470ea54df62c51854504">分析页面需要的数据和功能，我们可以得到以下结论：</div><ol start="1" class="notion-list notion-list-numbered notion-block-f7de315da99c4f9fa12e2a093bedabd0"><li>我们不仅需要通过输入框拿到输入内容，同时还需要将tag的内容反应到输入框中，所以需要同时提供<code class="notion-inline-code">searchText</code>和<code class="notion-inline-code">handleSearchChange</code>，即：</li><ol class="notion-list notion-list-numbered notion-block-f7de315da99c4f9fa12e2a093bedabd0"><pre class="notion-code language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
  <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span>
  <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Search for a tag or username<span class="token punctuation">"</span></span>
  <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{searchText}</span>
  <span class="token special-attr"><span class="token attr-name">onChange</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token value javascript language-javascript"><span class="token punctuation">{</span>handleSearchChange<span class="token punctuation">}</span></span></span></span>
  <span class="token attr-name">required</span>
  <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>search_input peer<span class="token punctuation">"</span></span>
<span class="token punctuation">/></span></span></code></pre></ol></ol><ol start="2" class="notion-list notion-list-numbered notion-block-9873e793400d40e9b6e79357dab841b2"><li>在输入框外面的form里，我们需要处理表单的提交行为，即敲击回车后，需要提交表单，即提供一个<code class="notion-inline-code">handleSubmit</code></li><ol class="notion-list notion-list-numbered notion-block-9873e793400d40e9b6e79357dab841b2"><pre class="notion-code language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>relative w-full flex-center<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">onSubmit</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token value javascript language-javascript"><span class="token punctuation">{</span>handleSubmit<span class="token punctuation">}</span></span></span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
    <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span>
    <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Search for a tag or username<span class="token punctuation">"</span></span>
    <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{searchText}</span>
    <span class="token special-attr"><span class="token attr-name">onChange</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token value javascript language-javascript"><span class="token punctuation">{</span>handleSearchChange<span class="token punctuation">}</span></span></span></span>
    <span class="token attr-name">required</span>
    <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>search_input peer<span class="token punctuation">"</span></span>
  <span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span></code></pre></ol></ol><ol start="3" class="notion-list notion-list-numbered notion-block-453509edacc643f19924aef0664d44f1"><li>在下面的信息流组件中，我们显然需要拿到数据，即提供一个<code class="notion-inline-code">posts</code>，且tag既然可以点击，我们也需要一个<code class="notion-inline-code">handleTagClick</code></li><ol class="notion-list notion-list-numbered notion-block-453509edacc643f19924aef0664d44f1"><pre class="notion-code language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PromptCardList</span> <span class="token attr-name">data</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{posts}</span> <span class="token attr-name">handleTagClick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{handleTagClick}</span> <span class="token punctuation">/></span></span></code></pre></ol></ol><ol start="4" class="notion-list notion-list-numbered notion-block-0d1717bed74f40b1a76ff19a0d11d9aa"><li>我们不光需要在hook内发送拿到feed数据的请求（主要是搜索），我们还需要考虑到，在页面挂载的时候，我们需要手动进行一次posts请求，拿到数据进行展示，所以需要提供一个<code class="notion-inline-code">fetchPosts</code> 。</li><ol class="notion-list notion-list-numbered notion-block-0d1717bed74f40b1a76ff19a0d11d9aa"><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span></code></pre></ol></ol><ol start="5" class="notion-list notion-list-numbered notion-block-be0b775017a24c89b1298926e5f7d704"><li>我们需要对输入内容具有响应性，这实际上是一个“防抖”功能的实现</li></ol><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-1bc2cee158ba47b4a0893ed5d864ef5b" data-id="1bc2cee158ba47b4a0893ed5d864ef5b"><span><div id="1bc2cee158ba47b4a0893ed5d864ef5b" class="notion-header-anchor"></div><a class="notion-hash-link" href="#1bc2cee158ba47b4a0893ed5d864ef5b" title="实现自定义Hook"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">实现自定义Hook</span></span></h4><div class="notion-text notion-block-6317d16d9b024c618f92dc92efc00e2c">当分析清楚了需求是什么之后，代码实现起来就没什么难度了。</div><div class="notion-text notion-block-de76d38ae3eb46e0abaa0a00b9881023">写这段防抖和tag点击逻辑的时候有点小插曲，就是防抖需要对输入框生效，但不应该对tag的点击生效，毕竟没人希望点击tag后需要等1s才能有相应，这就涉及一些清楚定时器的操作，这其中遇到的一些异步逻辑的debug过程，已经在上一篇博文中介绍了，有兴趣可以查看这篇文章：<!-- -->‣</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> useState<span class="token punctuation">,</span> useEffect<span class="token punctuation">,</span> useRef <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span>

<span class="token keyword">const</span> useSearchPosts <span class="token operator">=</span> <span class="token punctuation">(</span>initialSearchText <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">,</span> setSearchText<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span>initialSearchText<span class="token punctuation">)</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>posts<span class="token punctuation">,</span> setPosts<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> timeoutId <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span>

  <span class="token comment">// 获取所有 prompt</span>
  <span class="token keyword">const</span> <span class="token function-variable function">fetchPosts</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/prompt'</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token function">setPosts</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// 根据搜索关键词获取 prompt</span>
  <span class="token keyword">const</span> <span class="token function-variable function">fetchSearchPosts</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token parameter">search</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/prompt/search?searchText='</span> <span class="token operator">+</span> search<span class="token punctuation">)</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token function">setPosts</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

  <span class="token comment">// 防抖</span>
  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>searchText<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> newTimeout <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
        <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span>searchText<span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>
      timeoutId<span class="token punctuation">.</span>current <span class="token operator">=</span> newTimeout
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'set!'</span><span class="token punctuation">,</span> newTimeout<span class="token punctuation">)</span>
      <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>newTimeout<span class="token punctuation">)</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">]</span><span class="token punctuation">)</span>

  <span class="token keyword">const</span> <span class="token function-variable function">handleSearchChange</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">setSearchText</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> <span class="token function-variable function">handleSubmit</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'clear by submit'</span><span class="token punctuation">,</span> timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
    <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
    <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span>searchText<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> <span class="token function-variable function">handleTagClick</span> <span class="token operator">=</span> <span class="token parameter">tag</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">setSearchText</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span>
    <span class="token comment">// 需要等待searchText更新完毕，再清除副作用带来的定时器</span>
    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'clear by tag'</span><span class="token punctuation">,</span> timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
      <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
    <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span> <span class="token comment">// 立即执行搜索</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    searchText<span class="token punctuation">,</span>
    posts<span class="token punctuation">,</span>
    fetchPosts<span class="token punctuation">,</span>
    handleSearchChange<span class="token punctuation">,</span>
    handleSubmit<span class="token punctuation">,</span>
    handleTagClick
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> useSearchPosts
</code></pre></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Promptopia 开发笔记（2）——useState、useRef与异步逻辑]]></title>
            <link>/Promptopia-dev-note-state-ref-and-async</link>
            <guid>/Promptopia-dev-note-state-ref-and-async</guid>
            <pubDate>Fri, 14 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[从一次 debug 经历出发，讲讲 JS 里的异步机制如何影响你的代码执行？]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-98540d4118794bea9bdffa17af5cc06e"><div class="notion-viewport"></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-6255bd867ed4427ab36891a172384f60" data-id="6255bd867ed4427ab36891a172384f60"><span><div id="6255bd867ed4427ab36891a172384f60" class="notion-header-anchor"></div><a class="notion-hash-link" href="#6255bd867ed4427ab36891a172384f60" title="前言"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">前言</span></span></h3><div class="notion-text notion-block-69a41e6cf8e642a7bfd142dc85b3c7cf">书接上回，我在实现了搜索功能后，打算进一步实现 <code class="notion-inline-code">tag</code> 的点击功能，我的想法很简单，只需要在点击 <code class="notion-inline-code">tag</code> 后将 <code class="notion-inline-code">searchText</code> 这个变量设为 tag 的值，然后清除掉由 useEffect 设置的定时器，最后手动根据<code class="notion-inline-code">searchText</code> 发个请求更新一下 feed 就完事了，没想到这个过程竟然让我遇到了异步这个大坑。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-09a9f67f1a2042b299bce3e34fe82204" data-id="09a9f67f1a2042b299bce3e34fe82204"><span><div id="09a9f67f1a2042b299bce3e34fe82204" class="notion-header-anchor"></div><a class="notion-hash-link" href="#09a9f67f1a2042b299bce3e34fe82204" title="不可预见和难以理解的异步行为"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">不可预见和难以理解的异步行为</span></span></h3><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">,</span> setSearchText<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span>initialSearchText<span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>timeoutId<span class="token punctuation">,</span> setTimeoutId<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token comment">// 根据搜索关键词获取 prompt</span>
<span class="token keyword">const</span> <span class="token function-variable function">fetchSearchPosts</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/prompt/search?searchText='</span> <span class="token operator">+</span> searchText<span class="token punctuation">)</span>
  <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token function">setPosts</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// 防抖</span>
<span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>searchText<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> newTimeout <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'fetch by useEffect, searchText now: '</span><span class="token punctuation">,</span> searchText<span class="token punctuation">)</span>
      <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>
    <span class="token function">setTimeoutId</span><span class="token punctuation">(</span>newTimeout<span class="token punctuation">)</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'set!'</span><span class="token punctuation">,</span> newTimeout<span class="token punctuation">)</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>newTimeout<span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token function-variable function">handleTagClick</span> <span class="token operator">=</span> <span class="token parameter">tag</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token function">setSearchText</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span>
  
  <span class="token comment">// 立即触发类型，清除定时器</span>
  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'clear by tag'</span><span class="token punctuation">,</span> timeoutId<span class="token punctuation">)</span>
    <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">)</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'fetch by tag, searchText now: '</span><span class="token punctuation">,</span> searchText<span class="token punctuation">)</span>
    <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-e341689b004645c2854844a94dfd2beb">程序的执行入口是<code class="notion-inline-code">handleTagClick</code> ，所以我们先从这个部分看起，整段程序涉及两个<code class="notion-inline-code">setTimeout</code> ，两个 <code class="notion-inline-code">state</code> 变量，我们通过 console.log() 来搞清楚这些代码执行的顺序。</div><ol start="1" class="notion-list notion-list-numbered notion-block-27e94cd47ed04ec1aeda1e2aaf9c8fe4"><li><code class="notion-inline-code">setSearchText(tag)</code> 触发<code class="notion-inline-code">useEffect</code> ，首先执行<code class="notion-inline-code">useEffect</code> 内的部分；</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-ba49b083af8743ada94959454f1ca137"><li><code class="notion-inline-code">const newTimeout = setTimeout</code> 成为下一个宏任务，暂时挂起；</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-3562e595da77401fa2f91a8a3acaf2c4"><li><code class="notion-inline-code">setTimeoutId(newTimeout)</code> 将 <code class="notion-inline-code">timeoutId</code> 更新为最新的计时器；</li></ol><ol start="4" class="notion-list notion-list-numbered notion-block-5cdfca3789fd42288503cc10561b4f61"><li>打印 <code class="notion-inline-code">‘set!’, 127</code> </li></ol><ol start="5" class="notion-list notion-list-numbered notion-block-1d0eaf7b622e471f9581823dab52b9e4"><li>继续执行<code class="notion-inline-code">handleTagClick</code> 中剩余的代码</li><ol class="notion-list notion-list-numbered notion-block-1d0eaf7b622e471f9581823dab52b9e4"><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'clear by tag'</span><span class="token punctuation">,</span> timeoutId<span class="token punctuation">)</span>
  <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">)</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'fetch by tag, searchText now: '</span><span class="token punctuation">,</span> searchText<span class="token punctuation">)</span>
  <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span></code></pre><div class="notion-text notion-block-43ab05efcdc74340910a02754d13db0e">打印<code class="notion-inline-code">&#x27;clear by tag 120&#x27;</code> </div></ol></ol><ol start="6" class="notion-list notion-list-numbered notion-block-9af3aa9402de416ba5d4577c6cb8ca7e"><li>根据旧的 <code class="notion-inline-code">searchText</code> 发送请求<code class="notion-inline-code">fetchSearchPosts()</code> </li></ol><ol start="7" class="notion-list notion-list-numbered notion-block-3433ce67c9894e71b7dbb0aef42dd2fd"><li>等待 1s 的时间后，执行</li><ol class="notion-list notion-list-numbered notion-block-3433ce67c9894e71b7dbb0aef42dd2fd"><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">const</span> newTimeout <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'fetch by useEffect, searchText now: '</span><span class="token punctuation">,</span> searchText<span class="token punctuation">)</span>
  <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span></code></pre><div class="notion-text notion-block-c355e7658a2948b5be8acf60e88d59a9">内部的代码，根据最新的<code class="notion-inline-code">searchText</code> 发送请求</div></ol></ol><div class="notion-text notion-block-3a42b658892b47caa9b9fd93bf552d05">我们可以发现，出现了两个没有预期的行为：</div><ol start="1" class="notion-list notion-list-numbered notion-block-2244ceb444fc4bfaafa2eb7f152a3cbd"><li>定时器清理失败</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-59ab94bb67984a62b3e856e2fe2fe157"><li>由于定时器清理失败，发送了两次请求，但由本不该发送的那次请求完成了功能（即 <code class="notion-inline-code">useEffect</code> 内本来被清理的请求），而本来发挥作用的，在<code class="notion-inline-code">handleClick</code> 内的请求却发送了错误的请求（<code class="notion-inline-code">searchText</code>更新前的内容，即点击 tag 前的文本框内容）</li></ol><div class="notion-text notion-block-734b7d5092c64602b5ed042c826076dc">下面我们进行逐一分析</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-925fd5a87c934d65bf0d531ad30af318" data-id="925fd5a87c934d65bf0d531ad30af318"><span><div id="925fd5a87c934d65bf0d531ad30af318" class="notion-header-anchor"></div><a class="notion-hash-link" href="#925fd5a87c934d65bf0d531ad30af318" title="定时器清理失败"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">定时器清理失败</span></span></h4><div class="notion-text notion-block-ee2452f75f2a4019aa21ab8748957e4a">观察执行顺序我们可以看到：<b>虽然</b><code class="notion-inline-code"><b>handleTagClick</b></code><b>中的</b><code class="notion-inline-code"><b>setTimeout</b></code><b> 更晚执行，但他获取到的却不是最新的</b><code class="notion-inline-code"><b>timeoutId</b></code><b> ！</b>这是因为 JS 的<b>闭包机制</b>，使得<code class="notion-inline-code">handleTagClick</code>内的<code class="notion-inline-code">setTimeout</code>捕获了与它平级的作用域，也就是说即使它更晚执行，但获得的<code class="notion-inline-code">timeoutId</code> 实际上跟紧跟在<code class="notion-inline-code">setSearchText(tag)</code> 后获取的<code class="notion-inline-code">timeoutId</code> 是一样的，这也就解释了为什么明明 <code class="notion-inline-code">set</code> 在先，<code class="notion-inline-code">clear</code> 在后，但 <code class="notion-inline-code">clear</code> 的却不是该 <code class="notion-inline-code">clear</code> 的那个计时器。</div><div class="notion-callout notion-gray_background_co notion-block-effaff2a1e314af097b169ba984d22a6"><span class="notion-page-icon" role="img" aria-label="💡">💡</span><div class="notion-callout-text"><code class="notion-inline-code">state</code> 变量的更新是异步的，获取到的值可能并不符合你的预期，如果你需要获取根据 state 变量的最新值进行操作，你永远应该使用 useEffect 来监听这个变量获得最新值。</div></div><div class="notion-text notion-block-1ae8ed2a2fc1489a9336e7c3feee8293">定位到了问题，我们该如何解决呢，其实在<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://zh-hans.react.dev/learn/referencing-values-with-refs">官方文档</a>中，就已经推荐了使用 <code class="notion-inline-code">useRef</code> 来存储<code class="notion-inline-code">timeoutId</code> ，因为 <code class="notion-inline-code">state</code> 的更新是异步的，但 <code class="notion-inline-code">useRef</code> 的更新是同步的，你获取到的永远是最新的值！我们来看看 useRef 如何帮助我们突出闭包机制的重围：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">,</span> setSearchText<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span>initialSearchText<span class="token punctuation">)</span>
<span class="token keyword">const</span> timeoutId <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token function-variable function">handleTagClick</span> <span class="token operator">=</span> <span class="token parameter">tag</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token function">setSearchText</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span>
  <span class="token comment">// 需要等待searchText更新完毕，再清除副作用带来的定时器</span>
  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'clear by tag'</span><span class="token punctuation">,</span> timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
    <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
  <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 立即执行搜索</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-41bfecf6ed1f494cb9b453d03856b7f5">只在我们不关心侦听<code class="notion-inline-code">searchText</code>的 <code class="notion-inline-code">useEffect</code> 内发生了什么，我们只关心将 <code class="notion-inline-code">timeoutId</code> 换用 <code class="notion-inline-code">useRef</code> 存储后为什么就能解决问题？</div><div class="notion-text notion-block-7f923ba036664aec8daa5dd3d72119d8">根据闭包机制，<code class="notion-inline-code">handleTagClick&gt;setTimeout</code> 依旧捕获了跟<code class="notion-inline-code">setSearchText(tag)</code> 平级的<code class="notion-inline-code">timeoutId</code> ，这个变量依旧是不变的，但这个变量实际上指向了一个 <code class="notion-inline-code">ref</code> 对象，我们通过其<code class="notion-inline-code">current</code> 属性来访问它的值，因而在<code class="notion-inline-code">useEffect</code> 内发生的改变，依旧作用到了这里的<code class="notion-inline-code">timeoutId</code>上，成功让我们获取到了最新的<code class="notion-inline-code">timeoutId</code>！</div><div class="notion-callout notion-gray_background_co notion-block-23eba2115acf4ec6b283c01a23f63a62"><span class="notion-page-icon" role="img" aria-label="💡">💡</span><div class="notion-callout-text"><code class="notion-inline-code">useRef</code> 永远是最新的！</div></div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-d1268d43d75542aeb58cb7e73067d85c" data-id="d1268d43d75542aeb58cb7e73067d85c"><span><div id="d1268d43d75542aeb58cb7e73067d85c" class="notion-header-anchor"></div><a class="notion-hash-link" href="#d1268d43d75542aeb58cb7e73067d85c" title="请求错误"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">请求错误</span></span></h4><div class="notion-text notion-block-3eb53ca39ce74e26a76a7b0b03b43292">在成功修复了定时器清理失效的问题后，我们也就只会发送一次请求了，也就是<code class="notion-inline-code">handleTagClick</code> 中的请求，但根据前文提到的闭包机制，这里<code class="notion-inline-code">fetchSearchPosts()</code> 使用的依旧是旧的值，针对这个问题，我们可以简单的通过修改<code class="notion-inline-code">fetchSearchPosts()</code> 来实现，我们让它不要再依赖外部的 <code class="notion-inline-code">searchText</code> 来进行搜索，而是传入一个值，这里我们使用 tag 的值来进行搜索，修改后的代码如下：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> useState<span class="token punctuation">,</span> useEffect<span class="token punctuation">,</span> useRef <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span>

<span class="token keyword">const</span> useSearchPosts <span class="token operator">=</span> <span class="token punctuation">(</span>initialSearchText <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">,</span> setSearchText<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span>initialSearchText<span class="token punctuation">)</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>posts<span class="token punctuation">,</span> setPosts<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> timeoutId <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span>

  <span class="token comment">// 获取所有 prompt</span>
  <span class="token keyword">const</span> <span class="token function-variable function">fetchPosts</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/prompt'</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token function">setPosts</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// 根据搜索关键词获取 prompt</span>
  <span class="token keyword">const</span> <span class="token function-variable function">fetchSearchPosts</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token parameter">search</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/prompt/search?searchText='</span> <span class="token operator">+</span> search<span class="token punctuation">)</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token function">setPosts</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

  <span class="token comment">// 防抖</span>
  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>searchText<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> newTimeout <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
        <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span>searchText<span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>
      timeoutId<span class="token punctuation">.</span>current <span class="token operator">=</span> newTimeout
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'set!'</span><span class="token punctuation">,</span> newTimeout<span class="token punctuation">)</span>
      <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>newTimeout<span class="token punctuation">)</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">]</span><span class="token punctuation">)</span>

  <span class="token keyword">const</span> <span class="token function-variable function">handleSearchChange</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">setSearchText</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> <span class="token function-variable function">handleSubmit</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'clear by submit'</span><span class="token punctuation">,</span> timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
    <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
    <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span>searchText<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> <span class="token function-variable function">handleTagClick</span> <span class="token operator">=</span> <span class="token parameter">tag</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">setSearchText</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span>
    <span class="token comment">// 需要等待searchText更新完毕，再清除副作用带来的定时器</span>
    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'clear by tag'</span><span class="token punctuation">,</span> timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
      <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
    <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span> <span class="token comment">// 立即执行搜索</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    searchText<span class="token punctuation">,</span>
    posts<span class="token punctuation">,</span>
    fetchPosts<span class="token punctuation">,</span>
    handleSearchChange<span class="token punctuation">,</span>
    handleSubmit<span class="token punctuation">,</span>
    handleTagClick
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> useSearchPosts</code></pre><div class="notion-callout notion-gray_background_co notion-block-632f63b39d4f43ff8a0ce2a1cd59cf78"><span class="notion-page-icon" role="img" aria-label="💡">💡</span><div class="notion-callout-text">原先的<code class="notion-inline-code">fetchSearchPosts</code> 完全依赖于 <code class="notion-inline-code">searchText</code> 这个变量，不能根据传入参数动态决定请求参数，这样写并不合理；再加上我们点击 tag 后，我们设置<code class="notion-inline-code">searchText</code> 完全是为了让搜索框能显示我们点击的内容，而发送请求就没必要冒着变量不是最新的风险用<code class="notion-inline-code">searchText</code> 了，完全可以用 <code class="notion-inline-code">tag</code> 来发送请求。</div></div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Promptopia 开发笔记（1）——搜索功能的实现与最佳实践]]></title>
            <link>/Promptopia-dev-note-search-implementation</link>
            <guid>/Promptopia-dev-note-search-implementation</guid>
            <pubDate>Thu, 13 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[如何实现搜索功能？]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-d11cc5a5b9754a3a9d78739e3680b3fa"><div class="notion-viewport"></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-bded7c1e83314bd4a82ea23653ee0852" data-id="bded7c1e83314bd4a82ea23653ee0852"><span><div id="bded7c1e83314bd4a82ea23653ee0852" class="notion-header-anchor"></div><a class="notion-hash-link" href="#bded7c1e83314bd4a82ea23653ee0852" title="前言"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">前言</span></span></h3><div class="notion-text notion-block-9669e9e80a4c4be8bf7c22c4fc5aa64d">为了练手 Nextjs，最近在写一个项目，大概长这样，主要就是做一个大家分享大模型 Prompt 的网站：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-71b7714576574bcc9c526be0a8244783"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F01ff42de-5b53-40d7-a69e-999e3574e640%2F0d0d10f4-59ec-4229-8d63-40a5ae7f4647%2F%25E6%2588%25AA%25E5%25B1%258F2024-06-13_13.50.02.png?table=block&amp;id=71b77145-7657-4bcc-9c52-6be0a8244783&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-d6aa33187b204690aea80b0f2d47ace4">主要技术栈如下：</div><ol start="1" class="notion-list notion-list-numbered notion-block-627c6dac67774fe694ccec6c791b35f9"><li>使用 nextjs 作为整体框架</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-65a20aaee8b94d4e8dc187472701330f"><li>使用<code class="notion-inline-code">next-auth/react</code> 实现谷歌登录</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-29f2755eaf124c1a912ddcb04d23406e"><li>使用 <code class="notion-inline-code">MongoDB</code> 实现后端数据库（不得不感叹实在太方便了，直接就是一个在线服务，可以直接在 github page 这样的静态网站托管服务上部署网站，而且小体量还是免费的，良心企业）</li></ol><ol start="4" class="notion-list notion-list-numbered notion-block-69c8905d9e41402c92a4baa59c91b6ab"><li>使用<code class="notion-inline-code">tailwindCSS</code> 写样式（这个项目和我之前写的 <a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://musherm.github.io/Portofolio/">Portfolio</a> 项目同样都用了这个，对于写样式来说实在是太方便了）</li></ol><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-816fca4ebe594e65bbcf7793ef11d373" data-id="816fca4ebe594e65bbcf7793ef11d373"><span><div id="816fca4ebe594e65bbcf7793ef11d373" class="notion-header-anchor"></div><a class="notion-hash-link" href="#816fca4ebe594e65bbcf7793ef11d373" title="搜索功能实现"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">搜索功能实现</span></span></h3><div class="notion-text notion-block-835fbc90a9a84d8a9abd170bee692ad7">结合 <code class="notion-inline-code">MongoDB</code> 来实现增删改还是比较方便的，但在实现搜索功能方面如果只需要搜索 prompt 内容和 tag 内容还是比较简单的，但还要涉及搜索 username ，而 prompt 的 Schema 里只有 userId，需要对两个表进行关联搜索，实现起来还是需要动点脑子的。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-001a748bb965441e94383b7621087029" data-id="001a748bb965441e94383b7621087029"><span><div id="001a748bb965441e94383b7621087029" class="notion-header-anchor"></div><a class="notion-hash-link" href="#001a748bb965441e94383b7621087029" title="从搜索框开始考虑"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">从搜索框开始考虑</span></span></h4><div class="notion-text notion-block-b68a7980f1854a748d99a9ade364296f">首先我们来看这个输入框：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-2e9e51867b004aa7833cf75307720d7e"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F01ff42de-5b53-40d7-a69e-999e3574e640%2Fbc0dd8a4-ddf2-49c8-aaad-03568004248d%2F%25E6%2588%25AA%25E5%25B1%258F2024-06-13_13.50.02.png?table=block&amp;id=2e9e5186-7b00-4aa7-833c-f75307720d7e&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">handleSearchChange</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">setSearchText</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

<span class="token operator">&lt;</span>form className<span class="token operator">=</span><span class="token string">"relative w-full flex-center"</span> onSubmit<span class="token operator">=</span><span class="token punctuation">{</span>handleSubmit<span class="token punctuation">}</span><span class="token operator">></span>
  <span class="token operator">&lt;</span>input
    type<span class="token operator">=</span><span class="token string">"text"</span>
    placeholder<span class="token operator">=</span><span class="token string">"Search for a tag or username"</span>
    value<span class="token operator">=</span><span class="token punctuation">{</span>searchText<span class="token punctuation">}</span>
    onChange<span class="token operator">=</span><span class="token punctuation">{</span>handleSearchChange<span class="token punctuation">}</span>
    required
    className<span class="token operator">=</span><span class="token string">"search_input peer"</span>
  <span class="token operator">/</span><span class="token operator">></span>
<span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">></span></code></pre><div class="notion-text notion-block-acf106c62c134d6988679ca1ba1ed817">我们已经通过 <code class="notion-inline-code">onChange</code> 这个事件把输入框的内容获取到了 <code class="notion-inline-code">searchText</code> 这个变量，因为我想做到的效果是响应式地对输入框的改变进行搜索，也就是你一边输入，一边结果就跟着出来，而不需要你输入回车后才能有相应，所以实际上就是一个依赖于 <code class="notion-inline-code">searchText</code> 的 <code class="notion-inline-code">useEffect</code> 。总结而言，我们对输入框的需求如下：</div><ol start="1" class="notion-list notion-list-numbered notion-block-f327fceddfbb44adb0768e17488179d8"><li>针对输入内容响应式的发送请求；</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-cc1309f11982486d9656daedd6ac0b63"><li>按回车后立刻发送请求</li></ol><div class="notion-text notion-block-2a068c5399f54abfb0aa5e1aa6c80ddf">有了这样的思路，我们很容易写出这样的代码：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>searchText<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 搜索文本不为空，进行搜索</span>
      <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token comment">// 搜索文本为空，重新获取所有数据</span>
      <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">]</span><span class="token punctuation">)</span></code></pre><div class="notion-text notion-block-8e489a9ce1b44344b570796fc0caa8b9">但是我们不需要进行任何测试就可以轻易得出一个结论：这个体验并不好。因为我们响应式地针对<code class="notion-inline-code">searchText</code> 的每一次改变都发送了查询请求，这并不合理，当我输入 Apple 时，你应该等我输入完成再发送请求，而不是每当我输入一个字母就发送一次请求。</div><div class="notion-text notion-block-1a09b07d3fb141a58fa29d7ef8ba378b">因此我们实际上需要等你完成输入之后等个 1s 钟，确认你没有输入了，再发送请求，这个功能有一个学名——<b>防抖</b>。</div><details class="notion-toggle notion-block-3fe2cc94d08d42b281cb74b6f1c3c687"><summary>关于防抖</summary><div><div class="notion-text notion-block-79a047abd19f48678155cb5456dae6e7">笔者在本科上嵌入式的时候在设计键盘驱动时提到过这个防抖，当时这个功能主要是因为键盘的按键在按下时，输入信号可能会出现（以开关为例）多次通断，如果不在驱动层面进行处理，就会出现“双击”甚至“三击”，对这个现象的处理方式也叫“防抖”，所以实际上<b>防抖就是对短时间内重复输入的过滤</b>。</div></div></details><div class="notion-text notion-block-e5559266ebc1428b9bd226877799669b">那么我们该如何进行防抖处理呢，我们可以想到这样的方式：</div><ol start="1" class="notion-list notion-list-numbered notion-block-8991bef83f174cd48da355749a45df9d"><li>每次输入触发一个定时器，1s 后触发发送请求；</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-c7e908bd3b6347b9a7554d4a06e0d786"><li>在触发请求之前清空上一次的定时器，避免重复触发。</li></ol><div class="notion-text notion-block-da09f49f5e29442f9f000245d77583e7">对于如何清除上一次的定时器，我们可以在<code class="notion-inline-code">useEffect</code> 中的第一个参数传入的回调函数中返回一个用于清空定时器的函数，这个回调函数会在以下情况调用：</div><ol start="1" class="notion-list notion-list-numbered notion-block-86a8f63e958e4e9c89fece1403010856"><li>依赖项变化时；</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-84cdf4e779a6488ca5edc108f803585b"><li>组件卸载时。</li></ol><div class="notion-text notion-block-db10a8ec541f41d5b61ac6dc1b77b455">所以实际上我们只需要把清空定时器的函数返回就行了，不需要特意记录定时器的清空器；但还记得吗，我们还需要在用户输入回车后立即进行发送请求，所以实际上我们还是要记录最新的定时器钩子，方便我们在<code class="notion-inline-code">onSubmit</code> 事件上清除最新的定时器，并立即发送请求，所以具体的实现代码如下：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token comment">// 处理防抖，同时记录最新的 timeoutId，便于清除</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>timeoutId<span class="token punctuation">,</span> setTimeoutId<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token function-variable function">fetchSearchPosts</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/prompt/search?searchText='</span> <span class="token operator">+</span> searchText<span class="token punctuation">)</span>
  <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token function">setPosts</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>searchText<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> newTimeout <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>

    <span class="token function">setTimeoutId</span><span class="token punctuation">(</span>newTimeout<span class="token punctuation">)</span>
    <span class="token comment">// 返回一个清除 timeout 的函数，会在下次 useEffect 调用/组件卸载时执行（so sweet）</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>newTimeout<span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">]</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> <span class="token function-variable function">handleSubmit</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">)</span>
  <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-788943d200ff468eb771ba55f8526a3c" data-id="788943d200ff468eb771ba55f8526a3c"><span><div id="788943d200ff468eb771ba55f8526a3c" class="notion-header-anchor"></div><a class="notion-hash-link" href="#788943d200ff468eb771ba55f8526a3c" title="搜索 api 实现"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">搜索 api 实现</span></span></h4><div class="notion-text notion-block-38ef6535c4dc4543a258efb6008ca51c">在实现了搜索框的响应式输入查询、防抖后，我们需要具体实现查询的功能，我们的需求如下：</div><ol start="1" class="notion-list notion-list-numbered notion-block-52acee069a3146999130a17f851a7b75"><li>基于 username 进行查询</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-b9aeaf83134540eb93f48d7837436a82"><li>基于 tag 进行查询</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-506a19d1e7dd4a9b93e285ace381d1e0"><li>基于 prompt 的正文进行查询</li></ol><div class="notion-text notion-block-b51ea5755d084dabb8fce03a813ed18e">数据库表设计如下：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token comment">// Prompt</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Schema<span class="token punctuation">,</span> model<span class="token punctuation">,</span> models <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'mongoose'</span>

<span class="token keyword">const</span> PromptSchema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Schema</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">creator</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">type</span><span class="token operator">:</span> Schema<span class="token punctuation">.</span>Types<span class="token punctuation">.</span>ObjectId<span class="token punctuation">,</span>
    <span class="token literal-property property">ref</span><span class="token operator">:</span> <span class="token string">'User'</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">prompt</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">type</span><span class="token operator">:</span> String<span class="token punctuation">,</span>
    <span class="token literal-property property">required</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token string">'Prompt is required'</span><span class="token punctuation">]</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">tag</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">type</span><span class="token operator">:</span> String<span class="token punctuation">,</span>
    <span class="token literal-property property">required</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token string">'Tag is required'</span><span class="token punctuation">]</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> Prompt <span class="token operator">=</span> models<span class="token punctuation">.</span>Prompt <span class="token operator">||</span> <span class="token function">model</span><span class="token punctuation">(</span><span class="token string">'Prompt'</span><span class="token punctuation">,</span> PromptSchema<span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> Prompt
</code></pre><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token comment">// User</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Schema<span class="token punctuation">,</span> model<span class="token punctuation">,</span> models <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'mongoose'</span>

<span class="token keyword">const</span> UserSchema <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Schema</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">email</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">type</span><span class="token operator">:</span> String<span class="token punctuation">,</span>
    <span class="token literal-property property">unique</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token string">'Email already exists'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token literal-property property">required</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token string">'Email is required'</span><span class="token punctuation">]</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">username</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">type</span><span class="token operator">:</span> String<span class="token punctuation">,</span>
    <span class="token literal-property property">required</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token string">'Username is required'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token literal-property property">match</span><span class="token operator">:</span> <span class="token punctuation">[</span>
      <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^(?=.{8,20}$)(?![_.])(?!.*[_.]{2})[a-zA-Z0-9._]+(?&lt;![_.])$</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">,</span>
      <span class="token string">'Username invalid, it should contain 8-20 alphanumeric letters and be unique!'</span>
    <span class="token punctuation">]</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">image</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">type</span><span class="token operator">:</span> String
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> User <span class="token operator">=</span> models<span class="token punctuation">.</span>User <span class="token operator">||</span> <span class="token function">model</span><span class="token punctuation">(</span><span class="token string">'User'</span><span class="token punctuation">,</span> UserSchema<span class="token punctuation">)</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> User
</code></pre><div class="notion-text notion-block-b0e4a4bfafa849edb7b7550b3debba8d">其实后两个都比较简单，主要是基于 username 的查询涉及关联查询，我的实现方式如下：</div><ol start="1" class="notion-list notion-list-numbered notion-block-1c5eb60f64504ed7be30c2ddbddd9680"><li>首先在 User 表中搜索符合条件的 username 对应的 id；</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-bc5aaa6a3a124d2cafa5cacc76571a29"><li>然后在 Prompt 表中使用 or 条件（或者 and 条件，看你想做什么，我这里为了搜索出来结果多一点就用 or 了）加上 id 的判断</li></ol><div class="notion-text notion-block-fe3cf6f7d5e44907bc09726a440b4b2d">具体实现如下：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">import</span> Prompt <span class="token keyword">from</span> <span class="token string">'@/models/prompt'</span>
<span class="token keyword">import</span> User <span class="token keyword">from</span> <span class="token string">'@/models/user'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> connectToDB <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/utils/database'</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">GET</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token parameter">request</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> searchParams <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>url<span class="token punctuation">)</span>
  <span class="token keyword">const</span> searchText <span class="token operator">=</span> searchParams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'searchText'</span><span class="token punctuation">)</span>

  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token function">connectToDB</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token comment">// 拆分关键词</span>
    <span class="token keyword">const</span> keywordArray <span class="token operator">=</span> searchText<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">' '</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">kw</span> <span class="token operator">=></span> kw<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token string">''</span><span class="token punctuation">)</span>

    <span class="token comment">// 构建关键词表达式组</span>
    <span class="token keyword">const</span> regexArray <span class="token operator">=</span> keywordArray<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">kw</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">RegExp</span><span class="token punctuation">(</span>kw<span class="token punctuation">,</span> <span class="token string">'i'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

    <span class="token comment">// 查询所有满足条件的用户</span>
    <span class="token keyword">const</span> userConditions <span class="token operator">=</span> regexArray<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">kw</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">username</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">$regex</span><span class="token operator">:</span> kw <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> users <span class="token operator">=</span> <span class="token keyword">await</span> User<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">$or</span><span class="token operator">:</span> userConditions <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> userIds <span class="token operator">=</span> users<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">user</span> <span class="token operator">=></span> user<span class="token punctuation">.</span>_id<span class="token punctuation">)</span>

    <span class="token comment">// 查询满足条件的prompts</span>
    <span class="token keyword">const</span> prompts <span class="token operator">=</span> <span class="token keyword">await</span> Prompt<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token comment">// 用空格区分的关键词更合理的方式是用 and，但我这里先用 or 了，看起来显得多一点</span>
      <span class="token literal-property property">$or</span><span class="token operator">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span> <span class="token literal-property property">prompt</span><span class="token operator">:</span> <span class="token punctuation">{</span> $<span class="token keyword">in</span><span class="token operator">:</span> regexArray <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span> <span class="token literal-property property">tag</span><span class="token operator">:</span> <span class="token punctuation">{</span> $<span class="token keyword">in</span><span class="token operator">:</span> regexArray <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span> <span class="token literal-property property">creator</span><span class="token operator">:</span> <span class="token punctuation">{</span> $<span class="token keyword">in</span><span class="token operator">:</span> userIds <span class="token punctuation">}</span> <span class="token punctuation">}</span>
      <span class="token punctuation">]</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">populate</span><span class="token punctuation">(</span><span class="token string">'creator'</span><span class="token punctuation">)</span> <span class="token comment">// populate 方法，自动关联 User 表，但是前提需要在表设计时使用</span>
    <span class="token comment">// ref 指向你要关联的表</span>

    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>prompts<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">200</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span><span class="token string">'Failed to fetch prompts'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">500</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-0b7363c0cf544d1c9b6d25c4429fa929">这样我们就实现了搜索功能。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-6fcc565ad883460685361c28a6c81c21" data-id="6fcc565ad883460685361c28a6c81c21"><span><div id="6fcc565ad883460685361c28a6c81c21" class="notion-header-anchor"></div><a class="notion-hash-link" href="#6fcc565ad883460685361c28a6c81c21" title="后谈——优雅的封装"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">后谈——优雅的封装</span></span></h3><div class="notion-text notion-block-1a87d9a1061c4524a8fe635f5bae4188">我自诩为有代码洁癖（当然了，由于水平有限可能写出来的代码在别人看来也是💩，但至少我自己还要看的过去），之前实现的防抖逻辑全部都放在的 Feed 组件里，这并不优雅，Feed 组件应该只需要考虑这个组件内部的逻辑，但搜索这个功能其实跟 Feed 信息流没什么关系，只是这里有一个搜索框，而我们又需要调用搜索功能而已，所以我们不该把无关的功能全都堆在一个组件内部，这既不利于我们对搜索功能进行复用，也不利于别人理解我们的代码。所以实际上我们需要对这个搜索功能进行封装</div><div class="notion-text notion-block-21acc1f956a841cd8e3f9f3fe564cc2c">在前端开发中，这种用于处理与组件状态、生命周期相关的逻辑，我们一般称其为 Hook，并使用 <code class="notion-inline-code">use</code> 开头的函数对其进行封装，并组织在 src/hooks 这一文件夹中，所以我封装了以下 hook：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> useState<span class="token punctuation">,</span> useEffect <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span>

<span class="token keyword">const</span> useSearchPosts <span class="token operator">=</span> <span class="token punctuation">(</span>initialSearchText <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">,</span> setSearchText<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span>initialSearchText<span class="token punctuation">)</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>posts<span class="token punctuation">,</span> setPosts<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>timeoutId<span class="token punctuation">,</span> setTimeoutId<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span>

  <span class="token comment">// 获取所有 prompt</span>
  <span class="token keyword">const</span> <span class="token function-variable function">fetchPosts</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/prompt'</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token function">setPosts</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// 根据搜索关键词获取 prompt</span>
  <span class="token keyword">const</span> <span class="token function-variable function">fetchSearchPosts</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/prompt/search?searchText='</span> <span class="token operator">+</span> searchText<span class="token punctuation">)</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token function">setPosts</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

  <span class="token comment">// 防抖</span>
  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>searchText<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> newTimeout <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
        <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>

      <span class="token function">setTimeoutId</span><span class="token punctuation">(</span>newTimeout<span class="token punctuation">)</span>
      <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>newTimeout<span class="token punctuation">)</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>searchText<span class="token punctuation">]</span><span class="token punctuation">)</span>

  <span class="token keyword">const</span> <span class="token function-variable function">handleSearchChange</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">setSearchText</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> <span class="token function-variable function">handleSubmit</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeoutId<span class="token punctuation">)</span>
    <span class="token function">fetchSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    searchText<span class="token punctuation">,</span>
    posts<span class="token punctuation">,</span>
    fetchPosts<span class="token punctuation">,</span>
    handleSearchChange<span class="token punctuation">,</span>
    handleSubmit
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> useSearchPosts</code></pre><div class="notion-text notion-block-af9b181cad5846adba1e3d0c2c8ed46a">并在 Feed 组件内调用，使用方式如下：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token string">'use client'</span>

<span class="token keyword">import</span> React<span class="token punctuation">,</span> <span class="token punctuation">{</span> useContext<span class="token punctuation">,</span> useState<span class="token punctuation">,</span> useEffect <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span>
<span class="token keyword">import</span> PromptCard <span class="token keyword">from</span> <span class="token string">'./PromptCard'</span>
<span class="token keyword">import</span> basePathContext <span class="token keyword">from</span> <span class="token string">'@/context/basePathContext'</span>
<span class="token keyword">import</span> useSearchPosts <span class="token keyword">from</span> <span class="token string">'@/hooks/useSearchPosts'</span>

<span class="token keyword">const</span> <span class="token function-variable function">PromptCardList</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> data<span class="token punctuation">,</span> handleTagClick <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"mt-16 prompt_layout"</span><span class="token operator">></span>
      <span class="token punctuation">{</span>data<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">post</span> <span class="token operator">=></span> <span class="token punctuation">(</span>
        <span class="token operator">&lt;</span>PromptCard key<span class="token operator">=</span><span class="token punctuation">{</span>post<span class="token punctuation">.</span>id<span class="token punctuation">}</span> post<span class="token operator">=</span><span class="token punctuation">{</span>post<span class="token punctuation">}</span> handleTagClick<span class="token operator">=</span><span class="token punctuation">{</span>handleTagClick<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> <span class="token function-variable function">Feed</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> basePath <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> searchText<span class="token punctuation">,</span> posts<span class="token punctuation">,</span> fetchPosts<span class="token punctuation">,</span> handleSearchChange<span class="token punctuation">,</span> handleSubmit <span class="token punctuation">}</span> <span class="token operator">=</span>
    <span class="token function">useSearchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">fetchPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>basePathContext<span class="token punctuation">.</span>Provider value<span class="token operator">=</span><span class="token punctuation">{</span>basePath<span class="token punctuation">}</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>section className<span class="token operator">=</span><span class="token string">"feed"</span><span class="token operator">></span>
        <span class="token operator">&lt;</span>form className<span class="token operator">=</span><span class="token string">"relative w-full flex-center"</span> onSubmit<span class="token operator">=</span><span class="token punctuation">{</span>handleSubmit<span class="token punctuation">}</span><span class="token operator">></span>
          <span class="token operator">&lt;</span>input
            type<span class="token operator">=</span><span class="token string">"text"</span>
            placeholder<span class="token operator">=</span><span class="token string">"Search for a tag or username"</span>
            value<span class="token operator">=</span><span class="token punctuation">{</span>searchText<span class="token punctuation">}</span>
            onChange<span class="token operator">=</span><span class="token punctuation">{</span>handleSearchChange<span class="token punctuation">}</span>
            required
            className<span class="token operator">=</span><span class="token string">"search_input peer"</span>
          <span class="token operator">/</span><span class="token operator">></span>
        <span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">></span>

        <span class="token operator">&lt;</span>PromptCardList data<span class="token operator">=</span><span class="token punctuation">{</span>posts<span class="token punctuation">}</span> handleTagClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>section<span class="token operator">></span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>basePathContext<span class="token punctuation">.</span>Provider<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> Feed</code></pre><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-2e4810cac5d94127a379440fb8dcc1a1" data-id="2e4810cac5d94127a379440fb8dcc1a1"><span><div id="2e4810cac5d94127a379440fb8dcc1a1" class="notion-header-anchor"></div><a class="notion-hash-link" href="#2e4810cac5d94127a379440fb8dcc1a1" title="总结"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">总结</span></span></h3><div class="notion-text notion-block-1621950683f745efbe703e820c13ca71">跟着视频敲代码很简单，很多时候 youtuber 不解释他为什么这么写，你也不会去思考这个功能为什么要分这么多个文件？分这么多层？但一旦从头开始思考一个功能如何实现，需要思考的问题就很多了，比如我们实际上只是实现了一个非常简单的搜索功能，但本身涉及的有：</div><ol start="1" class="notion-list notion-list-numbered notion-block-26c635b0b50447d2aed45512e320d4d6"><li>防抖功能的实现</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-3997e5a64f494bf7afa187c5ab21fccc"><li>搜索 api 的实现</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-5e3fceaf6f92458fb567e2f735700f24"><li>自定义 Hook 的封装与调用</li></ol><hr class="notion-hr notion-block-88dc04912d374e47a42334e71d5948ec"/><div class="notion-text notion-block-9c7b0abb9e7944ad9e6c257412b48220">Wish you have a good day! 😇</div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Next.js 踩坑记（3）——路径体操（2）]]></title>
            <link>/path-middle2</link>
            <guid>/path-middle2</guid>
            <pubDate>Wed, 12 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[进一步讨论 nextjs 中静态资源的路径问题]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-9ca0671db5804105825cd8ef4c531d84"><div class="notion-viewport"></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-375e599595eb40e8b610cb2d2ac07160" data-id="375e599595eb40e8b610cb2d2ac07160"><span><div id="375e599595eb40e8b610cb2d2ac07160" class="notion-header-anchor"></div><a class="notion-hash-link" href="#375e599595eb40e8b610cb2d2ac07160" title="前言"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">前言</span></span></h3><div class="notion-text notion-block-250198b10c874b52b7ec6fa87384a8c9">在 <a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://musherm.github.io/nobelium/path-middle">Next.js 踩坑记（1）——路径体操</a> 中，我们已经讨论了在 Nextjs 中，当我们部署在一个“子路径”时，如何处理静态资源路径失效的问题，今天我们进一步探讨，内容总结如下：</div><ol start="1" class="notion-list notion-list-numbered notion-block-77e8fb3e6d4542899eef5a6ebafc0812"><li>使用<code class="notion-inline-code">nextconfig</code>中的<code class="notion-inline-code">publicRuntimeConfig</code> 来定义路径，相比于使用环境变量而言更优雅；</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-340966436813437fb4321512e26756c8"><li><code class="notion-inline-code">publicRuntimeConfig</code> 只能在服务端组件中获取，但往往需要在客户端使用，如何解决该矛盾？</li></ol><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-1bb39b4ff06044d8aee05869c1077813" data-id="1bb39b4ff06044d8aee05869c1077813"><span><div id="1bb39b4ff06044d8aee05869c1077813" class="notion-header-anchor"></div><a class="notion-hash-link" href="#1bb39b4ff06044d8aee05869c1077813" title="使用publicRuntimeConfig 定义子路径"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">使用<code class="notion-inline-code">publicRuntimeConfig</code> 定义子路径</span></span></h3><div class="notion-text notion-block-321add1f54b14f2fb2ac47fb42c9406d">在后续的开发过程中，我通过在<code class="notion-inline-code">nextconfig</code>中的<code class="notion-inline-code">publicRuntimeConfig</code> 定义一份一模一样的basePath和 assetPath，就像这样：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token comment">/** @type {import('next').NextConfig} */</span>
<span class="token keyword">const</span> isProd <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">===</span> <span class="token string">'production'</span>
<span class="token keyword">const</span> subBasePath <span class="token operator">=</span> <span class="token string">'/Promptopia'</span>
<span class="token keyword">const</span> subAssetPrefix <span class="token operator">=</span> subBasePath <span class="token operator">+</span> <span class="token string">'/'</span>
<span class="token keyword">const</span> nextConfig <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">basePath</span><span class="token operator">:</span> isProd <span class="token operator">?</span> subBasePath <span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
  <span class="token literal-property property">assetPrefix</span><span class="token operator">:</span> isProd <span class="token operator">?</span> subAssetPrefix <span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
  <span class="token literal-property property">publicRuntimeConfig</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">basePath</span><span class="token operator">:</span> isProd <span class="token operator">?</span> subBasePath <span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    <span class="token literal-property property">assetPrefix</span><span class="token operator">:</span> isProd <span class="token operator">?</span> subAssetPrefix <span class="token operator">:</span> <span class="token string">''</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">experimental</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">serverComponentsExternalPackages</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'mongoose'</span><span class="token punctuation">]</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">images</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">domains</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'lh3.googleusercontent.com'</span><span class="token punctuation">]</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> nextConfig
</code></pre><div class="notion-text notion-block-fc3f59a7b5b7494fa2605de881b7ee12">然后这样使用：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">import</span> getConfig <span class="token keyword">from</span> <span class="token string">'next/config'</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> publicRuntimeConfig <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">getConfig</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> basePath <span class="token punctuation">}</span> <span class="token operator">=</span> publicRuntimeConfig</code></pre><div class="notion-text notion-block-609efd3b5d0249ba8396dba1346fcd6a">这种方式其实跟前文使用环境变量的方式是一个道理，只是你不需要再在环境变量里 copy 一份这个路径了，能够统一修改。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-75bdf29c2ea54b9eb0d6c22e5f646680" data-id="75bdf29c2ea54b9eb0d6c22e5f646680"><span><div id="75bdf29c2ea54b9eb0d6c22e5f646680" class="notion-header-anchor"></div><a class="notion-hash-link" href="#75bdf29c2ea54b9eb0d6c22e5f646680" title="解决该方法只能在服务端组件中使用的问题"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">解决该方法只能在服务端组件中使用的问题</span></span></h3><div class="notion-text notion-block-92a101c24245439aba2c10aeb7b3f686">由于<code class="notion-inline-code">publicRuntimeConfig</code> 只能在服务端组件中获取，但又往往需要在客户端组件中使用，我们很容易想到使用 <code class="notion-inline-code">props</code> 传递，但当组件层级嵌套过深时，就会需要<b>逐级传递</b>，这是我们不需要看到的，在 React 中，我们使用 <a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://zh-hans.react.dev/learn/passing-data-deeply-with-context">Context</a> 组件来解决该问题。</div><div class="notion-text notion-block-def8484ac2e4445d8daf765890bb65c8">你可能会想到，我在一个组件通过<code class="notion-inline-code">publicRuntimeConfig</code> 来获取子路径，再通过 Context 组件提供上下文并导出，不就可以到处用了吗，这就带来我们面临的矛盾：</div><ul class="notion-list notion-list-disc notion-block-cc0d3664aaa04049bf5ace284f2acad4"><li><code class="notion-inline-code">publicRuntimeConfig</code> 只能在服务端调用，而 <code class="notion-inline-code">useContext</code> 则正好相反，只能在客户端调用（本质上和 <code class="notion-inline-code">useState</code> 一样，是 <code class="notion-inline-code">hook</code>）</li></ul><div class="notion-text notion-block-8469ef853eb04cd99d7ad72afbd5d6ce">所以“获取数据”和“提供上下文”这两件事就无法在同一层级完成，我们<b>必须</b>在处于更高层级的服务端组件中获取数据，通过 <code class="notion-inline-code">props</code> 传递给当前层级的客户端组件，再在客户端组件中通过 <code class="notion-inline-code">Context</code> 组件提供上下文给后面层级的组件，实践如下：</div><div class="notion-text notion-block-419ef09105db4feca523920a43c4e6c4">首先，我们需要创建一个 Context 组件：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token comment">// Context/basePathContext.js</span>
<span class="token string">'use client'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> createContext <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span>

<span class="token keyword">const</span> basePathContext <span class="token operator">=</span> <span class="token function">createContext</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> basePathContext</code></pre><div class="notion-text notion-block-b712d072f3d943019d82f7c960addfea">然后，我们需要在服务端组件中获取数据并传递给子组件：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token comment">// app/page.jsx</span>
<span class="token keyword">import</span> Feed <span class="token keyword">from</span> <span class="token string">'@/components/Feed'</span>
<span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span>
<span class="token keyword">import</span> getConfig <span class="token keyword">from</span> <span class="token string">'next/config'</span>

<span class="token keyword">const</span> <span class="token function-variable function">Home</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> publicRuntimeConfig <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">getConfig</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> basePath <span class="token punctuation">}</span> <span class="token operator">=</span> publicRuntimeConfig

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>section className<span class="token operator">=</span><span class="token string">"w-full flex-center flex-col"</span><span class="token operator">></span>
    <span class="token punctuation">{</span><span class="token comment">//***}</span>
      <span class="token operator">&lt;</span>Feed basePath<span class="token operator">=</span><span class="token punctuation">{</span>basePath<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span>
    <span class="token punctuation">{</span><span class="token comment">//***}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>section<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> Home
</code></pre><div class="notion-text notion-block-d459af8b9a304a2da9f8277ac25666ef">在子组件中，接受数据并调用<code class="notion-inline-code">basePathContext.Provider</code> 为更深层级提供上下文：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token comment">// components/Feed.jsx</span>
<span class="token string">'use client'</span>
<span class="token keyword">import</span> basePathContext <span class="token keyword">from</span> <span class="token string">'@/context/basePathContext'</span>

<span class="token keyword">const</span> <span class="token function-variable function">Feed</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> basePath <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token operator">&lt;</span>basePathContext<span class="token punctuation">.</span>Provider value<span class="token operator">=</span><span class="token punctuation">{</span>basePath<span class="token punctuation">}</span><span class="token operator">></span>
      <span class="token operator">&lt;</span>PromptCard<span class="token operator">/</span><span class="token operator">></span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>basePathContext<span class="token punctuation">.</span>Provider<span class="token operator">></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> Feed</code></pre><div class="notion-text notion-block-dcd50f7c307a445394428f4498225639">在后续的层级中，就可以通过<code class="notion-inline-code">useContext(basePathContext)</code>来获取这个值</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token comment">// components/PromptCard.jsx</span>
<span class="token keyword">import</span> React<span class="token punctuation">,</span> <span class="token punctuation">{</span> useContext<span class="token punctuation">,</span> useState <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span>
<span class="token keyword">import</span> basePathContext <span class="token keyword">from</span> <span class="token string">'@/context/basePathContext'</span>

<span class="token keyword">const</span> basePath <span class="token operator">=</span> <span class="token function">useContext</span><span class="token punctuation">(</span>basePathContext<span class="token punctuation">)</span></code></pre><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-06bacc51b4d248aab11e2a4a57d7ad5d" data-id="06bacc51b4d248aab11e2a4a57d7ad5d"><span><div id="06bacc51b4d248aab11e2a4a57d7ad5d" class="notion-header-anchor"></div><a class="notion-hash-link" href="#06bacc51b4d248aab11e2a4a57d7ad5d" title="总结"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">总结</span></span></h3><div class="notion-text notion-block-ab301f5803224be8a1f50446c95be5fa">也许这个问题我们可以通过哪里需要使用路径，哪里就调用环境变量来解决，但这并不只解决了一个简单的子路径部署问题，而是解决了“仅客户端可用”和“仅服务端可用”的矛盾问题，我们通过这样的方式，可以实现在客户端使用任意的“仅服务端可用变量”。</div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ 使用 Nobelium 从 Notion 构建你的个人博客]]></title>
            <link>/use-nobelium-to-construct-your-blog-from-notion</link>
            <guid>/use-nobelium-to-construct-your-blog-from-notion</guid>
            <pubDate>Mon, 03 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[从 0 开始介绍如何使用 Nobelium 构建你的个人博客，并部署到 Github Page]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-baacb5616a6243439e6abc1bba469a9c"><div class="notion-viewport"></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-95872343a890408d909100da7ed0fd58" data-id="95872343a890408d909100da7ed0fd58"><span><div id="95872343a890408d909100da7ed0fd58" class="notion-header-anchor"></div><a class="notion-hash-link" href="#95872343a890408d909100da7ed0fd58" title="Nobelium 主体部分"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Nobelium 主体部分</span></span></h3><div class="notion-text notion-block-f429650dc7714247a0d337d97b1e101a">建议直接使用我的 fork，该仓库基于<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://github.com/IMBlues/nobelium">https://github.com/IMBlues/nobelium</a>，在原先的基础上进行了改造，支持了部署在子目录下（比如<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://musherm.github.io/nobelium">https://musherm.github.io/nobelium</a>），同时加入了一些开关来控制首页的行为。</div><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-6e2830cd9a7c4c07bca8efe0365e4678" href="https://github.com/MusherM/nobelium"><div><div class="notion-bookmark-title">GitHub - MusherM/nobelium: Musher 的 Blog</div><div class="notion-bookmark-description">Musher 的 Blog. Contribute to MusherM/nobelium development by creating an account on GitHub.</div><div class="notion-bookmark-link"><img src="https://github.com/fluidicon.png" alt="GitHub - MusherM/nobelium: Musher 的 Blog" loading="lazy"/><div>https://github.com/MusherM/nobelium</div></div></div><div class="notion-bookmark-image"><img src="https://opengraph.githubassets.com/2937a8dce5d668f9fafbec44ee80cc3f79b527068d327b04d5c732edf37c524f/MusherM/nobelium" alt="GitHub - MusherM/nobelium: Musher 的 Blog" loading="lazy"/></div></a></div><div class="notion-text notion-block-c77e06492d1a4c229481d5ed6d456883">fork 一份到自己的仓库里，然后 git clone 下来以便后续更改。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-9e87fc04bc804a60ada3a46efe850be5" data-id="9e87fc04bc804a60ada3a46efe850be5"><span><div id="9e87fc04bc804a60ada3a46efe850be5" class="notion-header-anchor"></div><a class="notion-hash-link" href="#9e87fc04bc804a60ada3a46efe850be5" title="构建你的 notion 页"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">构建你的 notion 页</span></span></h4><div class="notion-text notion-block-f5cd49e80b10415d98a1919f345b477a">由于 nobelium 直接从 notion 获取并生成博客，自然需要从一个<b>公开</b>的 notion 页面获取内容，所以需要复制以下页面到自己的 notion 里，并将其设置为公开（也有不公开的办法，但这种方法需要获取 notion 登录的 token，比较麻烦，不推荐）</div><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-59b82e9e7bfa4470af3a8be52fe86e62" href="https://craigary.notion.site/ee99f65a23ab44f8ac80270122ee8138?v=6d187f15b9104d5598623622d42e6d02"><div><div class="notion-bookmark-title">NOBELIUM Template | Notion</div><div class="notion-bookmark-description">Built with Notion, the all-in-one connected workspace with publishing capabilities.</div><div class="notion-bookmark-link"><img src="https://www.notion.so/front-static/logo-ios.png" alt="NOBELIUM Template | Notion" loading="lazy"/><div>https://craigary.notion.site/ee99f65a23ab44f8ac80270122ee8138?v=6d187f15b9104d5598623622d42e6d02</div></div></div><div class="notion-bookmark-image"><img src="https://craigary.notion.site/images/meta/notion-wordmark.png" alt="NOBELIUM Template | Notion" loading="lazy"/></div></a></div><div class="notion-row notion-block-92041f938e744c738d593ce4d2b64630"><div class="notion-column notion-block-d03b8ed72809473fb129e3548766679c" style="width:calc((100% - (1 * min(32px, 4vw))) * 0.4375)"><ol start="1" class="notion-list notion-list-numbered notion-block-9b02c2c2ef034d3682e0e53f06040e5e"><li>在复制过来的自己的 notion 仓库里，检查 share 设定，确保其设置为 Publish。</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-542f31de841c41809226c44829b2d57d"><li>获取你的 notionPageId（即图中[id].notion.site/后面，?v前面的部分）</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-ce6b2e33b28b4f01bdd23a066be9c1a0"><li>将其填入你 clone 下来的项目中的 <code class="notion-inline-code">blog.config.js</code> 中的notionPageId 字段</li></ol><ol start="4" class="notion-list notion-list-numbered notion-block-c0bf921a400a490e87c905cec8f29505"><li>顺便完善其他相关字段，比如 author, title 等</li></ol></div><div class="notion-spacer"></div><div class="notion-column notion-block-d76e0750577c4ab0a96c62b93c05e06d" style="width:calc((100% - (1 * min(32px, 4vw))) * 0.5625)"><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-a237310343c4494c89bffcdec3f11cad"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:384px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F01ff42de-5b53-40d7-a69e-999e3574e640%2Fb6eadbfb-46f9-4a8e-bf19-b153efda45fc%2F%25E6%2588%25AA%25E5%25B1%258F2024-06-03_14.11.00.png?table=block&amp;id=a2373103-43c4-494c-89bf-fcdec3f11cad&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure></div><div class="notion-spacer"></div></div><details class="notion-toggle notion-block-60ef3c2ecc01434c940100f3840cb369"><summary>在我复制的 notion 页中，database 中的各个字段都有什么作用？</summary><div><ul class="notion-list notion-list-disc notion-block-1e2c20d7a3714f79860d17a9624be8c0"><li>slug 定义了每篇文章对应的路由名称，比如我的 blog 主页是<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://musherm.github.io/nobelium">https://musherm.github.io/nobelium</a> ，一篇文章的 slug 设置为 path-middle，那么他最终的地址就是<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://musherm.github.io/nobelium/path-middle">https://musherm.github.io/nobelium/path-middle</a>。</li></ul><ul class="notion-list notion-list-disc notion-block-e4a6f95cd16c4027a1086c9d4c32c261"><li>status 和 type 共同决定了这篇文章是否会出现在你最终构建的博客中，只有<code class="notion-inline-code">status === ‘Published’ &amp;&amp; type === ‘post’</code> 才会出现在你的博客里，前者不为 Published 代表这篇博客还在写作状态中，没有准备好发表；后者如果是 page，则代表它并不是一篇博客。</li></ul></div></details><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-85aacd7ca81c422c8357b5e37e9e78c7" data-id="85aacd7ca81c422c8357b5e37e9e78c7"><span><div id="85aacd7ca81c422c8357b5e37e9e78c7" class="notion-header-anchor"></div><a class="notion-hash-link" href="#85aacd7ca81c422c8357b5e37e9e78c7" title="阶段性检验"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">阶段性检验</span></span></h4><div class="notion-text notion-block-cdcf161e4eca42b8b498d996fb441089">使用 <code class="notion-inline-code">npm install</code> 命令安装好依赖后，使用 <code class="notion-inline-code">npm run dev</code> 在本地运行开发环境，做了如上设置后，你应该已经能够得到一个在本地运行的博客系统，并且已经能抓取到你设置的 notion 页内的博文。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-899d442308514584bd4eda9ed8cd6718" data-id="899d442308514584bd4eda9ed8cd6718"><span><div id="899d442308514584bd4eda9ed8cd6718" class="notion-header-anchor"></div><a class="notion-hash-link" href="#899d442308514584bd4eda9ed8cd6718" title="部署到 Github Page"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">部署到 Github Page</span></span></h3><div class="notion-text notion-block-89672518e41645a3876d04d5f868c098">部署到 Github Page，首先需要前往你的仓库→settings→Pages→选择部署方式Deploy from branch，如果你已经进行过推送，则这里应该已经有 gh-pages 这个分支（如果没有，请 push 一次触发预先定义的 github action，如果 action 编译失败也没关系，后面我们还需要进行修改），选择从 gh-pages 这个分支进行构建。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-d9e077e741124a03b721f34769ef24f8" data-id="d9e077e741124a03b721f34769ef24f8"><span><div id="d9e077e741124a03b721f34769ef24f8" class="notion-header-anchor"></div><a class="notion-hash-link" href="#d9e077e741124a03b721f34769ef24f8" title="子路径名称修改（如果你直接 fork 了我的 repo，可以跳过这部分）"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">子路径名称修改（如果你直接 fork 了我的 repo，可以跳过这部分）</span></span></h4><div class="notion-text notion-block-0203ac1bb6f74c3cbb67d31c79b74092">前面提到过，作者改造的仓库能够支持部署在子目录下的 github page，比如<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://musherm.github.io/nobelium">https://musherm.github.io/nobelium</a>，但每个人的子目录都不一样，所以你需要进行修改，如果你没有自己的域名，你的域名应该是[id].github.io/[repo]，其中，[id]是你的 github 昵称，[repo] 是你的仓库名称，你需要对后者进行修改。</div><details class="notion-toggle notion-block-f2a6d5dd08cb41fab34ca48fff8ac480"><summary>如何知道你的 repo 叫什么名字？</summary><div><div class="notion-text notion-block-1f61e69767c04c1aae472594dfe344c9">从你的项目主页后面的名字就可以得知。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-162c779e86ca4838a137ab30ebe48bf8"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F01ff42de-5b53-40d7-a69e-999e3574e640%2Fffd0062c-646a-4327-869a-459794482beb%2F%25E6%2588%25AA%25E5%25B1%258F2024-06-03_14.35.36.png?table=block&amp;id=162c779e-86ca-4838-a137-ab30ebe48bf8&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure></div></details><div class="notion-text notion-block-f4f0a4c4beb74220babfa3b46db9f022">打开你的 <code class="notion-inline-code">next.config.js</code> ，你应该能在开头看到如下字段</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">const</span> isProd <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">===</span> <span class="token string">"production"</span><span class="token punctuation">;</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">basePath</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/nobelium"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token literal-property property">assetPrefix</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/nobelium/"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token literal-property property">publicRuntimeConfig</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">basePath</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/nobelium"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
    <span class="token literal-property property">assetPrefix</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/nobelium/"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token operator">...</span></code></pre><div class="notion-text notion-block-cfee65400dba4873b1cc071f9dfcaf96">你需要将其中所有的<code class="notion-inline-code">nobelium</code> 替换成你的 repo 名称。</div><div class="notion-text notion-block-d660d148fc5a4c63bdf4c72cf2cae87e">保存修改后进行 commit，然后 push 到 Github，此时应该触发了 Github Action 进行编译，你应该能够在这里看到一些细节。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-21b063f941094dcc84b3e20fa43b241d"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F01ff42de-5b53-40d7-a69e-999e3574e640%2F3e897e85-6a35-4f21-b305-fd0639bc25ef%2F%25E6%2588%25AA%25E5%25B1%258F2024-06-03_14.39.55.png?table=block&amp;id=21b063f9-4109-4dcc-84b3-e20fa43b241d&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-d3aab4cd00404da5a35aae4d8150b6c2">部署成功后，你可以尝试访问你的 github page 来查看是否正常，你的 github page 链接能够通过 Settings→Pages 来查询到。</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-caa2c6ebeeda40668b088fd09146d630"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F01ff42de-5b53-40d7-a69e-999e3574e640%2Fb62005e8-03b7-46af-9a7a-ac76fd4b29d9%2F%25E6%2588%25AA%25E5%25B1%258F2024-06-03_14.40.56.png?table=block&amp;id=caa2c6eb-eeda-4066-8b08-8fd09146d630&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-9d2e93e556ae45848e8030e6b3c56a69">其实到这里如果一切顺利的话，你已经完成了从 notion 构建个人博客的工作流了，之后你只需要在 notion 书写你的内容，然后去 Github Action 去手动触发一下就行了（因为 Github Action 并不知道你的 notion 状态，所以没办法自动化这个过程）</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-b3f8bca88cd4401a8bb4ee0738e26a20" data-id="b3f8bca88cd4401a8bb4ee0738e26a20"><span><div id="b3f8bca88cd4401a8bb4ee0738e26a20" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b3f8bca88cd4401a8bb4ee0738e26a20" title="尽善尽美——完善你的博客数据分析系统和评论系统"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">尽善尽美——完善你的博客数据分析系统和评论系统</span></span></h3><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-b5e73c601e294c378c765d35c60b20b9" data-id="b5e73c601e294c378c765d35c60b20b9"><span><div id="b5e73c601e294c378c765d35c60b20b9" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b5e73c601e294c378c765d35c60b20b9" title="数据分析"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">数据分析</span></span></h4><div class="notion-text notion-block-d7000f55fb2a406198b7bd44369a420b">知道你的博客访问数据是非常有趣的，尤其是自建博客没有像论坛一样的访问量统计功能，所以你可能想要通过其他方法知道有多少人，哪些人访问了你的博客，所幸 Nobelium 内嵌支持了 Google Analytics。</div><div class="notion-text notion-block-a99b7279b269440e86ba1b50c09bc21d">要使用 Google Analytics，你首先需要注册一个账号，前往<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://analytics.google.com/">https://analytics.google.com/</a>，根据提示一路往下，直到你获取了你的 ga id，它应该是以G-开头的一串代码，在 <code class="notion-inline-code">blog.config.js</code> 中，将以下字段中的<code class="notion-inline-code">provider</code>设置为’ga’，将 <code class="notion-inline-code">measurementId</code> 设置为你自己的 ga id，这就能启用数据分析了。</div><div class="notion-callout notion-gray_background_co notion-block-020ccc6f0ef54c18a535e889d71491d6"><span class="notion-page-icon" role="img" aria-label="💡">💡</span><div class="notion-callout-text">如果你需要在本地测试，你可能需要打开隐私模式并关闭“魔法”，因为 Adblock 之类的插件可能会拦截 ga 的请求，并且如果你的魔法规则包含广告过滤，可能也会拒绝 ga 的请求。</div></div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token literal-property property">analytics</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">provider</span><span class="token operator">:</span> <span class="token string">"ga"</span><span class="token punctuation">,</span> <span class="token comment">// Currently we support Google Analytics and Ackee, please fill with 'ga' or 'ackee', leave it empty to disable it.</span>
    <span class="token literal-property property">ackeeConfig</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">tracker</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token comment">// e.g 'https://ackee.craigary.net/tracker.js'</span>
      <span class="token literal-property property">dataAckeeServer</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token comment">// e.g https://ackee.craigary.net , don't end with a slash</span>
      <span class="token literal-property property">domainId</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token comment">// e.g '0e2257a8-54d4-4847-91a1-0311ea48cc7b'</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">gaConfig</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">measurementId</span><span class="token operator">:</span> <span class="token string">"G-xxxxxxx"</span><span class="token punctuation">,</span> <span class="token comment">// e.g: G-XXXXXXXXXX</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-57c3eac71c6140e3bf6254e8b636e728" data-id="57c3eac71c6140e3bf6254e8b636e728"><span><div id="57c3eac71c6140e3bf6254e8b636e728" class="notion-header-anchor"></div><a class="notion-hash-link" href="#57c3eac71c6140e3bf6254e8b636e728" title="评论系统"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">评论系统</span></span></h4><div class="notion-text notion-block-e56f5c4bc12946c2bd90452aeb888438">Amazing！依托于 <a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://utteranc.es/">Utterances</a>，你现在可以在<b>静态</b>的博客上实现评论系统！如果说你感觉到岁月静好，那就一定有人替你负重前行，一般来说实现评论系统都需要一个后端服务器来处理和存储评论，那么在静态服务器上是谁在替你负重前行呢，答案是 Github Issue。</div><div class="notion-text notion-block-c8837d76623042c882b19fc9509a2ad2"><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://utteranc.es/">Utterances</a> 通过自动创建和搜索 Github Issue，来实现评论系统（即你的评论实际上直接发送到了 Github Issue，别人阅读评论也是直接通过 Github Issue 来抓取）。</div><div class="notion-text notion-block-764acdcd6f874aef8c072c86b5052f6e">由于<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://utteranc.es/">Utterances</a>的文档并不直接针对 Nobelium 的支持，所以本文给出一个详细的配置过程。</div><ol start="1" class="notion-list notion-list-numbered notion-block-59a76925da2140728407fd7d1698ad14"><li>如果你的仓库是 fork 的，请先检查自己的仓库是否打开了 Issue，前往 Settings→General→Features 检查 Issue 是否打开，如果没有就打开它；</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-60508a2dad9b4c31a5c6f4bf603c5446"><li>确保你的仓库是 Public 状态，否则别人将无法访问你的 Issue；</li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-8cf5eeab31204f2d968dd3ac55e74b84"><li>安装 <a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://github.com/apps/utterances">Utterance App</a>，至少允许它访问你用于部署博客的那个 repo。</li></ol><ol start="4" class="notion-list notion-list-numbered notion-block-63a15e9d7d6648c388d56148c9a8d59a"><li>在<code class="notion-inline-code">blog.config.js</code> 中修改如下内容</li></ol><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token literal-property property">comment</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token comment">// support provider: gitalk, utterances, cusdis</span>
    <span class="token literal-property property">provider</span><span class="token operator">:</span> <span class="token string">"utterances"</span><span class="token punctuation">,</span> <span class="token comment">// 确保这里是utterances</span>
    <span class="token literal-property property">gitalkConfig</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">repo</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token comment">// 这里不用改</span>
      <span class="token literal-property property">owner</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
      <span class="token literal-property property">admin</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token literal-property property">clientID</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
      <span class="token literal-property property">clientSecret</span><span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
      <span class="token literal-property property">distractionFreeMode</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">utterancesConfig</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">repo</span><span class="token operator">:</span> <span class="token string">"MusherM/nobelium"</span><span class="token punctuation">,</span>
<span class="token comment">// 这里填id/repo，比如我的 repo 写着MusherM/nobelium，那这里就填这个</span>
<span class="token comment">// 如果你是 fork 的，应该只需要改前面的 id 就行</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-8e78400ff7bd453d8283546422df57c0"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:480px;max-width:100%;flex-direction:column"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F01ff42de-5b53-40d7-a69e-999e3574e640%2Fffd0062c-646a-4327-869a-459794482beb%2F%25E6%2588%25AA%25E5%25B1%258F2024-06-03_14.35.36.png?table=block&amp;id=8e78400f-f7bd-453d-8283-546422df57c0&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-8fb1c5ce1a494723991f153418fbfbfa">不出意外的话，评论系统现在应该已经出现在了你的每篇文章底部。</div><hr class="notion-hr notion-block-240d99c16a864ef59bef7c7bcf4ece44"/><details class="notion-toggle notion-block-e89a2aa6918a41ab84b954ea16bce3b6"><summary><a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://utteranc.es/">Utterances</a> 评论系统的缺陷</summary><div><ul class="notion-list notion-list-disc notion-block-f893da37fd1e44b3a4238552f19d0023"><li>由于使用了 Github Issue 来管理评论，所以显而易见地需要评论者使用 Github 账号进行登录</li></ul><ul class="notion-list notion-list-disc notion-block-612cb0a4e55f4d3ca711ad5e7c6ba52f"><li>当然，无论是评论还是阅读评论，都需要读者能够拥有正常访问 Github 的网络环境（当然你都在 Github Page 部署网页了，可能也不需要担心这个问题了）</li></ul></div></details><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-dda3dd05c6f54bf28e15d2a1a14cf8c4" data-id="dda3dd05c6f54bf28e15d2a1a14cf8c4"><span><div id="dda3dd05c6f54bf28e15d2a1a14cf8c4" class="notion-header-anchor"></div><a class="notion-hash-link" href="#dda3dd05c6f54bf28e15d2a1a14cf8c4" title="后谈"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">后谈</span></span></h3><div class="notion-text notion-block-2e22803e20e9425cb2b50e86e08da4ae">这里主要聊一聊我都做了哪些改动，让这个项目能够部署在子目录中，改动主要在以下内容：</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-9c5debf880114020a2ebee37ee1263e7" data-id="9c5debf880114020a2ebee37ee1263e7"><span><div id="9c5debf880114020a2ebee37ee1263e7" class="notion-header-anchor"></div><a class="notion-hash-link" href="#9c5debf880114020a2ebee37ee1263e7" title="静态资源访问"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">静态资源访问</span></span></h4><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">const</span> isProd <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">===</span> <span class="token string">"production"</span><span class="token punctuation">;</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">basePath</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/nobelium"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token literal-property property">assetPrefix</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/nobelium/"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token literal-property property">publicRuntimeConfig</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">basePath</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/nobelium"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
    <span class="token literal-property property">assetPrefix</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/nobelium/"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre><div class="notion-text notion-block-68967f529d134cb08fc5a9405107f4a1">这里在生产环境引入了一个路径名，从而让其能正确索引到静态资源。光是这样还不够，我们通过全局搜索&lt;link 可以发现这样的标签</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token operator">&lt;</span>link
    rel<span class="token operator">=</span><span class="token string">"preload"</span>
    href<span class="token operator">=</span><span class="token string">"/fonts/SourceSerif.var.woff2"</span>
    <span class="token keyword">as</span><span class="token operator">=</span><span class="token string">"font"</span>
    type<span class="token operator">=</span><span class="token string">"font/woff2"</span>
    crossOrigin<span class="token operator">=</span><span class="token string">"anonymous"</span>
  <span class="token operator">/</span><span class="token operator">></span></code></pre><div class="notion-text notion-block-ee69dca19f2c4358b03279782906a7bc">这个项目通过一个写死的路径索引了一些字体，需要对这个路径进行一定的处理，不然你可能会在控制台看到一堆报错，同时你的网页还会失去 favicon（即小图标）。</div><div class="notion-text notion-block-df9e3994f745497facb446d21095c2d0">就像我在<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://musherm.github.io/nobelium/path-middle">https://musherm.github.io/nobelium/path-middle</a> 里介绍的，basePath和 assetPath 对 <code class="notion-inline-code">&lt;link&gt;</code> 标签是无效的，他只对 Nextjs 自己定义的 <code class="notion-inline-code">&lt;Link&gt;</code> 标签有效，所以你需要手动引入 basePath，所以我们 <code class="notion-inline-code">nextconfig</code> 中，还通过<code class="notion-inline-code">publicRuntimeConfig</code> 字段重新定义了一遍 basePath，这是因为只有这个字段内的变量才能在运行时引入。</div><div class="notion-text notion-block-6a67d0f58a9f49889e09d88ec2e26790">所以我们可以对所有的 &lt;link&gt; 标签手动引入 basePath，以上述的标签为例，将其修改成这样：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token comment">// 记得先引入</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> basePath <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@/next.config"</span><span class="token punctuation">;</span>
<span class="token operator">&lt;</span>link
  rel<span class="token operator">=</span><span class="token string">"preload"</span>
  href<span class="token operator">=</span><span class="token punctuation">{</span>basePath <span class="token operator">+</span> <span class="token string">"/fonts/SourceSerif.var.woff2"</span><span class="token punctuation">}</span>
  <span class="token keyword">as</span><span class="token operator">=</span><span class="token string">"font"</span>
  type<span class="token operator">=</span><span class="token string">"font/woff2"</span>
  crossOrigin<span class="token operator">=</span><span class="token string">"anonymous"</span>
<span class="token operator">/</span><span class="token operator">></span></code></pre><div class="notion-text notion-block-8befb23d2f6f42499ec3baed520e8ec9">将以上改动应用到所有的 &lt;link&gt; 标签上，即可完善剩余的静态资源访问，同时你可能会注意到你的网站有小图标了！Congratulations 😇</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-d35f964157564546a8c77ecdfda6795f" data-id="d35f964157564546a8c77ecdfda6795f"><span><div id="d35f964157564546a8c77ecdfda6795f" class="notion-header-anchor"></div><a class="notion-hash-link" href="#d35f964157564546a8c77ecdfda6795f" title="细枝末节"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">细枝末节</span></span></h4><div class="notion-text notion-block-0f0984c56ede4b5f84846de95edcae3f">作为一个完美主义者，我不能接受我的网站上出现任何的死链，在原有的 fork 中，我发现首页的关于和冲浪两个链接点了是找不到的，我实际上也不需要这两个链接，所以干脆找了找，在 header 添加了对于这两个组件的控制选项。</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">NavBar</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> locale <span class="token operator">=</span> <span class="token function">useLocale</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> links <span class="token operator">=</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> locale<span class="token punctuation">.</span><span class="token constant">NAV</span><span class="token punctuation">.</span><span class="token constant">ABOUT</span><span class="token punctuation">,</span> <span class="token literal-property property">to</span><span class="token operator">:</span> <span class="token string">"/about"</span><span class="token punctuation">,</span> <span class="token literal-property property">show</span><span class="token operator">:</span> <span class="token constant">BLOG</span><span class="token punctuation">.</span>showAbout <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
      <span class="token literal-property property">name</span><span class="token operator">:</span> locale<span class="token punctuation">.</span><span class="token constant">NAV</span><span class="token punctuation">.</span><span class="token constant">SURFING</span><span class="token punctuation">,</span>
      <span class="token literal-property property">to</span><span class="token operator">:</span> <span class="token string">"/tag/surfing"</span><span class="token punctuation">,</span>
      <span class="token literal-property property">show</span><span class="token operator">:</span> <span class="token constant">BLOG</span><span class="token punctuation">.</span>showSurfing<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> locale<span class="token punctuation">.</span><span class="token constant">NAV</span><span class="token punctuation">.</span><span class="token constant">SEARCH</span><span class="token punctuation">,</span> <span class="token literal-property property">to</span><span class="token operator">:</span> <span class="token string">"/search"</span><span class="token punctuation">,</span> <span class="token literal-property property">show</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre><div class="notion-text notion-block-13a6c5a9527049f9b355a5fd12081ea1">同时在 blogConfig 里添加了两个控制选项，来把这两个东西关掉。</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token literal-property property">showAbout</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token literal-property property">showSurfing</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span></code></pre><div class="notion-blank notion-block-603dd37e982c476f834205f4cb4f1e2c"> </div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ Next.js 踩坑记（1）——路径体操]]></title>
            <link>/path-middle</link>
            <guid>/path-middle</guid>
            <pubDate>Wed, 29 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[如何愉快的将你的 Nextjs 项目部署上 Github Page？]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-c62e81a103a94b5c9a554e56867de3e4"><div class="notion-viewport"></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-18c8656e71814ba5a5d2afb80ddf0ad9" data-id="18c8656e71814ba5a5d2afb80ddf0ad9"><span><div id="18c8656e71814ba5a5d2afb80ddf0ad9" class="notion-header-anchor"></div><a class="notion-hash-link" href="#18c8656e71814ba5a5d2afb80ddf0ad9" title="引言"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">引言</span></span></h3><div class="notion-text notion-block-f7f890b707114b1f96c8227213b48736">前段时间入坑了 React 开发，学习了基本语法后就决定找个项目跟着写写，由于b站上相关的资源重复率实在是太高了，我实在不想再写一个苍穹外卖之类的东西，于是在 YouTube 上找到了该视频，该项目是一个作品集项目，我觉得作为我 React 的 startup 非常不错，而且这个网页的审美也很合我的品味。</div><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-5733c7e25c254785a351304cda8f2311" href="https://www.youtube.com/watch?v=FTH6Dn3AyIQ&amp;list=PL6QREj8te1P6wX9m5KnicnDVEucbOPsqR"><div><div class="notion-bookmark-title">Build and Deploy an Amazing Developer Portfolio with Next JS and Framer Motion</div><div class="notion-bookmark-description">Learn how to build and deploy a modern developer portfolio with animations using Next.js. Perfect for showcasing your web development skills!

⭐ Hostinger - https://hostinger.com/mastery10
Use the code JAVASCRIPTMASTERY to save up to 91% on all yearly hosting plans.

🌟 Sentry - https://bit.ly/4abT6PG

🎨 Portfolio Figma Design - https://resource.jsmastery.pro/minimal-portfolio
📘 Portfolio Best Practices Guide - https://resource.jsmastery.pro/portfolio-best-practices
🤖 Special Discord Forum - https://resource.jsmastery.pro/portfolio-discord

🌟 Become a top 1% Next.js 14 developer: https://jsmastery.pro/next14
🚀 Skyrocket your career in 4 months: https://jsmastery.pro/masterclass

📚 Materials/References:
GitHub Repository (give it a star ⭐): https://github.com/adrianhajdin/portfolio
README (assets &amp; code): https://github.com/adrianhajdin/portfolio/blob/main/README.md

💻 Join our Discord Community - https://discord.com/invite/n6EdbFJ
🐦 Follow us on Twitter: http://twitter.com/jsmasterypro
🖼️ Follow us on Instagram: http://instagram.com/javascriptmastery

💼 Business Inquiries: contact@jsmastery.pro

Time Stamps 👇
00:00:00 — Intro
00:05:40 — Project Setup
00:14:48 — Hero Section
00:40:30 — Bento Grid
01:23:18 — Recent Projects
01:43:40 — Sentry
01:52:56 — Testimonials
02:06:00 — Work Experience
02:14:18 — My Approach Section
02:26:24 — Footer
02:35:13 — Fixing Bugs
02:39:19 — Deployment</div><div class="notion-bookmark-link"><img src="https://www.youtube.com/s/desktop/11f8caf2/img/favicon_144x144.png" alt="Build and Deploy an Amazing Developer Portfolio with Next JS and Framer Motion" loading="lazy"/><div>https://www.youtube.com/watch?v=FTH6Dn3AyIQ&amp;list=PL6QREj8te1P6wX9m5KnicnDVEucbOPsqR</div></div></div><div class="notion-bookmark-image"><img src="https://i.ytimg.com/vi/FTH6Dn3AyIQ/maxresdefault.jpg" alt="Build and Deploy an Amazing Developer Portfolio with Next JS and Framer Motion" loading="lazy"/></div></a></div><div class="notion-text notion-block-93319b5a226345aeb6c54062ce2147af">本着 up 敲一行我就敲一行的学习精神，两个半小时的项目也不难敲，最后项目上线的时候该博主使用 Vercel 进行部署，本着能省一点是一点的原则，我决定使用 Github Page 功能进行部署，主要原因如下</div><ol start="1" class="notion-list notion-list-numbered notion-block-2366d56a504b459680452bee0b0456c9"><li>免费，真的良心</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-4ae0b04b71b04a08829413e05dc21754"><li>本来这东西就是静态的，没什么后端服务，我想着给他渲染成纯静态页面丢上去不就完了呗</li></ol><div class="notion-text notion-block-3d9bf1a82a2147679e4fd71f1ea135fd">于是，开搞！</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-d7339765d3d444cd98d4786f05653467" data-id="d7339765d3d444cd98d4786f05653467"><span><div id="d7339765d3d444cd98d4786f05653467" class="notion-header-anchor"></div><a class="notion-hash-link" href="#d7339765d3d444cd98d4786f05653467" title="困境"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">困境</span></span></h3><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-22f184ac2f4b4398ba27bd7f2bbdb643"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F01ff42de-5b53-40d7-a69e-999e3574e640%2F6a314909-e6df-4fac-a08c-1d1b16dc7717%2FUntitled.png?table=block&amp;id=22f184ac-2f4b-4398-ba27-bd7f2bbdb643&amp;cache=v2" loading="lazy" alt="notion image" decoding="async"/></div></figure><div class="notion-text notion-block-0169015b827f40319b5af395469dc896">首先，我遇到了第一个问题，部署页面有两种方式，我到底该使用 Github Action 方式，还是 Deploy from a branch 方式？网上介绍的大多是第二种方式，这里笔者推荐使用第一种方式，我们可以看到这两种方式配置好之后的工作流是什么样的，孰优孰劣大家自有评判</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-b2eb601e7b3f4bca9920e67b7cdc0090" data-id="b2eb601e7b3f4bca9920e67b7cdc0090"><span><div id="b2eb601e7b3f4bca9920e67b7cdc0090" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b2eb601e7b3f4bca9920e67b7cdc0090" title="Github Action 方式"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Github Action 方式</span></span></h4><div class="notion-text notion-block-aaa4cc1394a74546b77d33e855d17786">以 main 分支为当前分支为例，只需要将当前 commit push 上去→触发 Github Action，直接完成 page 更新。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-b01de668c8de46239a4c3a6140d1fff7" data-id="b01de668c8de46239a4c3a6140d1fff7"><span><div id="b01de668c8de46239a4c3a6140d1fff7" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b01de668c8de46239a4c3a6140d1fff7" title="Deploy from a branch 方式"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">Deploy from a branch 方式</span></span></h4><div class="notion-text notion-block-3ec443dfe6aa4f398ae5f212e053b98a">本地修改后，在本地进行编译→使用 gh-pages 将编译结果推送到 gh-pages 分支，完成更新，相比于前一种方式，除了修改代码，还需要手动编译，再推送，非常不优雅。</div><hr class="notion-hr notion-block-e32aef135d0e4b098a1803fcc6eec67f"/><div class="notion-text notion-block-ddc314df393344bfac8e3bf684f84430">关于如何使用 Github Action 方式进行部署，选中该方式之后，github 会弹一个工作流文件出来，检测到当前项目为 Nextjs 项目后会自动推荐 Nextjs 的工作流，我们可以暂时先用这个工作流，后面再进行修改。</div><div class="notion-text notion-block-4ca4c07c08e64474a7b15f1fa250c40b">此时按理来说，只要你没有使用什么在服务端不可用的生命周期钩子，你的代码会顺利在 Github 的服务器上完成编译，并成为该项目的 Page，但事情并没有这么简单。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-0a4c3d88bc0f49768be9047295ce0b24" data-id="0a4c3d88bc0f49768be9047295ce0b24"><span><div id="0a4c3d88bc0f49768be9047295ce0b24" class="notion-header-anchor"></div><a class="notion-hash-link" href="#0a4c3d88bc0f49768be9047295ce0b24" title="静态资源路径问题"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">静态资源路径问题</span></span></h3><div class="notion-text notion-block-adcd58349e494d868073d96f56fa86bb">当我第一次看到 Github Action 中的工作流中，编译成功的绿标出现后，我非常兴奋的打开了我的网页，它长这样：</div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-6a7be33806574d99848be2eb6d82291d"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column;height:100%"><img style="object-fit:contain" src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F01ff42de-5b53-40d7-a69e-999e3574e640%2F9ca960f8-e2dd-496e-b01d-4dbddff831bf%2FUntitled.png?table=block&amp;id=6a7be338-0657-4d99-848b-e2eb6d82291d&amp;cache=v2" loading="lazy" alt="这是我直接打开本地编译的结果，仅作演示" decoding="async"/><figcaption class="notion-asset-caption">这是我直接打开本地编译的结果，仅作演示</figcaption></div></figure><div class="notion-text notion-block-6b477b4e2d1d40ae86086829efe6a2fc">坑爹呢这是 😵‍💫，此时打开开发者工具发现，除了当前页面的 html 外，不论是处于<code class="notion-inline-code">_next/static</code> 路径下的静态资源（主要是js和css），还是处于<code class="notion-inline-code">public</code> 路径下的静态资源（主要是图片），资源路径通通是错的，这个项目的路径应该是<code class="notion-inline-code">musherm.github.io/Portofolio/</code> ，但所有的资源都指向<code class="notion-inline-code">musherm.github.io/</code> ，换言之，我需要给所有的静态资源路径前面加上一个<code class="notion-inline-code">’Portofolio/’</code> ，查阅官方文档，我们可以发现，nextjs.config.mjs这一文件中，存在两个选项与路径有关，他们分别是：</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-ce1110b89e114b1c8802daa02e4d3045" data-id="ce1110b89e114b1c8802daa02e4d3045"><span><div id="ce1110b89e114b1c8802daa02e4d3045" class="notion-header-anchor"></div><a class="notion-hash-link" href="#ce1110b89e114b1c8802daa02e4d3045" title="basePath"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">basePath</span></span></h4><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-741f507e062e4142a3e7757219c22cd0" href="https://nextjs.org/docs/app/api-reference/next-config-js/basePath"><div><div class="notion-bookmark-title">next.config.js Options: basePath</div><div class="notion-bookmark-description">Use `basePath` to deploy a Next.js application under a sub-path of a domain.</div><div class="notion-bookmark-link"><img src="https://nextjs.org/favicon.ico" alt="next.config.js Options: basePath" loading="lazy"/><div>https://nextjs.org/docs/app/api-reference/next-config-js/basePath</div></div></div><div class="notion-bookmark-image"><img src="https://nextjs.org/api/docs-og?title=next.config.js%20Options:%20basePath" alt="next.config.js Options: basePath" loading="lazy"/></div></a></div><div class="notion-text notion-block-06a0763c80124f00aaafdb5f3fd83556">该选项用于在一个子路径下部署项目，他主要在两种场景下生效：</div><ol start="1" class="notion-list notion-list-numbered notion-block-651c03fc7c2d4fc4a28f2fd7ca957aa6"><li>Links：当使用<code class="notion-inline-code">next/link</code>和<code class="notion-inline-code">next/router</code> 时，会自动添加basePath，也就是说<code class="notion-inline-code">&lt;Link href=&quot;/about&quot;&gt;About Page&lt;/Link&gt;</code> 会输出<code class="notion-inline-code">&lt;a href=&quot;/docs/about&quot;&gt;About Page&lt;/a&gt;</code> （<code class="notion-inline-code">basePath=’/docs’</code>）</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-ab119fb430df420fb41a70c4a81a8653"><li>Image：当使用<code class="notion-inline-code">next/image</code> 时，需要手动添加basePath，说白了就是不管你引用图片用的是<code class="notion-inline-code">&lt;img&gt;</code>还是<code class="notion-inline-code">&lt;Image&gt;</code> ，你通通需要手动把这个路径写完整</li></ol><div class="notion-callout notion-gray_background_co notion-block-5f8e024486e245778d1a3b0bfd0b70a4"><span class="notion-page-icon" role="img" aria-label="💡">💡</span><div class="notion-callout-text">官方既然考虑到了图片资源会出现这个问题，为啥不能通过 basePath 自动给他加上呢，无语了。</div></div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-460d264b99a948c58302d4dc80d24ca3" data-id="460d264b99a948c58302d4dc80d24ca3"><span><div id="460d264b99a948c58302d4dc80d24ca3" class="notion-header-anchor"></div><a class="notion-hash-link" href="#460d264b99a948c58302d4dc80d24ca3" title="assetPrefix"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">assetPrefix</span></span></h4><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-4435f1e4fe294c81805b4150c3a69c09" href="https://nextjs.org/docs/app/api-reference/next-config-js/assetPrefix"><div><div class="notion-bookmark-title">next.config.js Options: assetPrefix</div><div class="notion-bookmark-description">Learn how to use the assetPrefix config option to configure your CDN.</div><div class="notion-bookmark-link"><img src="https://nextjs.org/favicon.ico" alt="next.config.js Options: assetPrefix" loading="lazy"/><div>https://nextjs.org/docs/app/api-reference/next-config-js/assetPrefix</div></div></div><div class="notion-bookmark-image"><img src="https://nextjs.org/api/docs-og?title=next.config.js%20Options:%20assetPrefix" alt="next.config.js Options: assetPrefix" loading="lazy"/></div></a></div><div class="notion-text notion-block-65dc149178ad4d22b5aa71a0fc0b7283">该选项仅针对<code class="notion-inline-code">_next/static</code> 下的静态js css资源，不会影响public下的资源，看来这一选项可以帮助我们解决js和css找不到的问题！</div><div class="notion-text notion-block-d7587d67f8324da684ebb18c8af9bc4a">修改我们的<code class="notion-inline-code">nextconfig</code> 为这样：</div><pre class="notion-code language-yaml"><code class="language-yaml"><span class="token comment">/** @type {import('next').NextConfig} */</span>
<span class="token keyword">const</span> nextConfig <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token string">"export"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">basePath</span><span class="token operator">:</span> <span class="token string">"/Portofolio"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">assetPrefix</span><span class="token operator">:</span> <span class="token string">"/Portofolio/"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">images</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">unoptimized</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> nextConfig<span class="token punctuation">;</span></code></pre><div class="notion-text notion-block-05b26bcf294643e39ce2b8313b0c0e0d">好，至少我们的js和css确实找到了，但是事情还没有解决，我们的img标签下的所有图片，无一例外，还是指向了根目录，根据官方文档，你必须手动给他加上。</div><div class="notion-text notion-block-9fe079cfbd4745a7b3269ab6982ba956">比如一个<code class="notion-inline-code">img</code>标签，你修改之前它长这样：</div><pre class="notion-code language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span>
  <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{icon}</span>
  <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{icon}</span>
  <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>p-2<span class="token punctuation">"</span></span>
<span class="token punctuation">/></span></span></code></pre><div class="notion-text notion-block-51953bc8b96c4cc6ad34f03f8895f548">修改之后呢，它应该长这样：</div><pre class="notion-code language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span>
  <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{</span>
    <span class="token attr-name">process.env.NEXT_PUBLIC_ASSET_PREFIX</span> <span class="token attr-name">+</span> <span class="token attr-name">icon</span>
  <span class="token attr-name">}</span>
  <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{icon}</span>
  <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>p-2<span class="token punctuation">"</span></span>
<span class="token punctuation">/></span></span></code></pre><div class="notion-text notion-block-6f600606692541a188b2cf4a083d5120">同时修改你的<code class="notion-inline-code">nextconfig</code>，提供一下<code class="notion-inline-code">NEXT_PUBLIC_ASSET_PREFIX</code> 这个环境变量，当然，你也可以通过.env .env.local 来提供，但我不喜欢把配置文件分散在好几个文件上，尽量集中管理比较清晰：</div><pre class="notion-code language-yaml"><code class="language-yaml"><span class="token comment">/** @type {import('next').NextConfig} */</span>
<span class="token keyword">const</span> nextConfig <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token string">"export"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">basePath</span><span class="token operator">:</span> <span class="token string">"/Portofolio"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">assetPrefix</span><span class="token operator">:</span> <span class="token string">"/Portofolio/"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">images</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">unoptimized</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">env</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token constant">NEXT_PUBLIC_ASSET_PREFIX</span><span class="token operator">:</span> <span class="token string">"/Portofolio"</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> nextConfig<span class="token punctuation">;</span></code></pre><div class="notion-text notion-block-9a461ca4e93745a3a0f3464f0bf8c2f1">到这里，你在编译的代码如果直接打开，资源路径应该已经加上了你的相对路径名，如果直接 push 上去，Github Action 部署还是会不成功，因为服务器端并不会读取你在 nextconfig 里定义的环境变量，你还需要在工作流中提供这个环境变量，所幸这个操作其实也很简单：打开用于定义工作流的文件，在我的环境下是.github/workflows/nextjs.yml，并在编译的时候提供环境变量，比如这样</div><pre class="notion-code language-html"><code class="language-html">- name: Build with Next.js
  run: ${{ steps.detect-package-manager.outputs.runner }} next build
  # Add this
  env:
    NEXT_PUBLIC_ASSET_PREFIX: /Portofolio</code></pre><div class="notion-text notion-block-61ae3e6125f04f0fb05ef8c16a96b7e8">添加的这一行，会在服务器端编译的过程中提供这个环境变量，从而保证编译后对于public文件夹下的静态资源访问正确，到这里，我们就已经完成了对于 Nextjs 项目的 Github Page 部署。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-1a092113c89c4e12b01d221610815496" data-id="1a092113c89c4e12b01d221610815496"><span><div id="1a092113c89c4e12b01d221610815496" class="notion-header-anchor"></div><a class="notion-hash-link" href="#1a092113c89c4e12b01d221610815496" title="后谈"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">后谈</span></span></h3><div class="notion-text notion-block-f33dfea57e2342cb87801e27f2d0dcb1">细心的小伙伴可能发现，写死 <code class="notion-inline-code">basePath</code> <code class="notion-inline-code">assetPrefix</code> <code class="notion-inline-code">NEXT_PUBLIC_ASSET_PREFIX</code>这些变量，当你在本地dev环境开发时，你的首页会从<code class="notion-inline-code">localhost:3000</code>变成<code class="notion-inline-code">localhost:3000/Portofolio</code> ，如果你能接受，那其实也还行，但如果想“不行，我在本地开发的时候，我就要在<code class="notion-inline-code">localhost:3000</code>直接访问
！”那你可以继续看下去（没错，说的就是我自己，我真的忍受不了每次从终端点开这个链接，还需要手动补全后面的路径）。答案是，你可以像<code class="notion-inline-code">NEXT_PUBLIC_ASSET_PREFIX</code> 一样，再引入一个<code class="notion-inline-code">NODE_ENV</code> 来判断当前的开发环境，从而在生产环境使用子路径，但在开发环境不实用，此时<code class="notion-inline-code">nextconfig</code> 修改如下：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token comment">/** @type {import('next').NextConfig} */</span>
<span class="token keyword">const</span> isProd <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">===</span> <span class="token string">"production"</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> nextConfig <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token string">"export"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">basePath</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/Portofolio"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token literal-property property">assetPrefix</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/Portofolio/"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token literal-property property">images</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">unoptimized</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">env</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token constant">NEXT_PUBLIC_ASSET_PREFIX</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/Portofolio"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> nextConfig<span class="token punctuation">;</span></code></pre><div class="notion-text notion-block-7d6e37c8d4e040048cdc942ffb521cc9">同时修改<code class="notion-inline-code">img</code>标签的<code class="notion-inline-code">src</code>如下</div><pre class="notion-code language-html"><code class="language-html">&lt;img 
	src={
    (process.env.NEXT_PUBLIC_ASSET_PREFIX || "") + "/profile.svg"
  }
  alt="profile"
/></code></pre><div class="notion-text notion-block-0c0eef999dc949c8b879afd744cfaa95">你可能想问为什么？按理来说我在dev环境已经指定了<code class="notion-inline-code">NEXT_PUBLIC_ASSET_PREFIX</code>为空啊？这就是<code class="notion-inline-code">Nextjs</code> 的又一大坑，<code class="notion-inline-code">Nextjs</code> 的环境变量无法指定为空！（至少据我所知）如果你像这样试图指定为空，它会变成<code class="notion-inline-code">undefined</code> ，因而你需要通过<code class="notion-inline-code">(process.env.NEXT_PUBLIC_ASSET_PREFIX || &quot;&quot;)</code> 这样的表达式体操来让<code class="notion-inline-code">undefined</code> 变成真正的空。</div><div class="notion-text notion-block-df693aea004043f1876efd43f7bb5f49">此外，你还需要修改 Github Action 的流程定义文件，从而指定编译环境为 <code class="notion-inline-code">production</code>。</div><pre class="notion-code language-yaml"><code class="language-yaml"><span class="token operator">-</span> name<span class="token operator">:</span> Build <span class="token keyword">with</span> Next<span class="token punctuation">.</span>js
  <span class="token literal-property property">run</span><span class="token operator">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> steps<span class="token punctuation">.</span>detect<span class="token operator">-</span><span class="token keyword">package</span><span class="token operator">-</span>manager<span class="token punctuation">.</span>outputs<span class="token punctuation">.</span>runner <span class="token punctuation">}</span><span class="token punctuation">}</span> next build
  <span class="token literal-property property">env</span><span class="token operator">:</span>
    <span class="token constant">NODE_ENV</span><span class="token operator">:</span> production
    <span class="token constant">NEXT_PUBLIC_ASSET_PREFIX</span><span class="token operator">:</span> <span class="token operator">/</span>Portofolio</code></pre><div class="notion-text notion-block-ce0a67933863424d936e4f059261f1eb">于是，经过这么一番折腾，我们终于调好了环境！</div><ol start="1" class="notion-list notion-list-numbered notion-block-aabd027e0bcb4ab39a1faca793487aa5"><li>在 Github Action 侧，我们通过 <code class="notion-inline-code">basePath</code> <code class="notion-inline-code">assetPrefix</code> <code class="notion-inline-code">NEXT_PUBLIC_ASSET_PREFIX</code> 三兄弟，完成了对<code class="notion-inline-code">css</code> <code class="notion-inline-code">js</code>  <code class="notion-inline-code">img</code> 这些静态资源的路径补全；</li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-a4bf6e14b49a480d92faba672089c9a6"><li>在本地 dev 侧，我们将这三兄弟全部设为空，从而保持原有的根路径访问</li></ol><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-a0b6b14e17f24df4b8ff9b681fe04ece" data-id="a0b6b14e17f24df4b8ff9b681fe04ece"><span><div id="a0b6b14e17f24df4b8ff9b681fe04ece" class="notion-header-anchor"></div><a class="notion-hash-link" href="#a0b6b14e17f24df4b8ff9b681fe04ece" title="后后谈"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">后后谈</span></span></h3><div class="notion-text notion-block-72562cc3d9b04440bd5b33adfb9b699d">总结这篇博客查阅资料时，我查到了这个东西</div><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-1c7c643a565649f4a869bf3289b3616a" href="https://nextjs.org/docs/app/api-reference/next-config-js/images"><div><div class="notion-bookmark-title">next.config.js Options: images</div><div class="notion-bookmark-description">Custom configuration for the next/image loader</div><div class="notion-bookmark-link"><img src="https://nextjs.org/favicon.ico" alt="next.config.js Options: images" loading="lazy"/><div>https://nextjs.org/docs/app/api-reference/next-config-js/images</div></div></div><div class="notion-bookmark-image"><img src="https://nextjs.org/api/docs-og?title=next.config.js%20Options:%20images" alt="next.config.js Options: images" loading="lazy"/></div></a></div><div class="notion-text notion-block-739c9da903e1405bb196d7b7c496321b">由于你可以像这样定义<code class="notion-inline-code">image loader</code> ：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">myImageLoader</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> src<span class="token punctuation">,</span> width<span class="token punctuation">,</span> quality <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://example.com/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>src<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">?w=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&amp;q=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>quality <span class="token operator">||</span> <span class="token number">75</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-770e0fdd9fd94d36be070c71bfa7a1bd">所以理论上，你也可以通过在imageLoader内部拼接src，从而补全路径，但笔者懒得写了，就先放到 todo List 吧。</div><hr class="notion-hr notion-block-1fb90465bc1342deb7aa7f9df1233aef"/><div class="notion-text notion-block-5d410fe2f59544d2a00921a7764075c4">成果展示：</div><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-585a070e8d5d440587103bd80405e37c" href="https://musherm.github.io/Portofolio/"><div><div class="notion-bookmark-title">Musher&#x27;s Portfolio</div><div class="notion-bookmark-description">Modern &amp; Minimalist JS Mastery Portfolio</div><div class="notion-bookmark-link"><div>https://musherm.github.io/Portofolio/</div></div></div></a></div><div class="notion-blank notion-block-fc39e43ba324400a86549d35cf2efd55"> </div></main>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Next.js 踩坑记（2）——开发依赖]]></title>
            <link>/dev-dependencies</link>
            <guid>/dev-dependencies</guid>
            <pubDate>Wed, 29 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Nextjs迷踪之——我只改了github action中的NODE_ENV=’production’，为什么编译就崩了？]]></description>
            <content:encoded><![CDATA[<main class="notion light-mode notion-page notion-block-5514a197e2664487a3ce95ae17f26bdb"><div class="notion-viewport"></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-ff5f5b878eb14112b9cef17983c53c0c" data-id="ff5f5b878eb14112b9cef17983c53c0c"><span><div id="ff5f5b878eb14112b9cef17983c53c0c" class="notion-header-anchor"></div><a class="notion-hash-link" href="#ff5f5b878eb14112b9cef17983c53c0c" title="引言"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">引言</span></span></h3><div class="notion-text notion-block-2b746c5ea66d4c07a6bad92162b07e71">在写<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://musherm.github.io/Portofolio/">个人作品集</a>这个项目时，由于遇到了让人非常恼火的路径问题，于是有了这篇博客</div><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-f48c6ae33f3649199a9380e905f002da" href="https://musherm.github.io/nobelium/path-middle"><div><div class="notion-bookmark-title">Next.js 踩坑记（1）——路径体操</div><div class="notion-bookmark-description">如何愉快的将你的 Nextjs 项目部署上 Github Page？</div><div class="notion-bookmark-link"><img src="https://musherm.github.io/apple-touch-icon.png" alt="Next.js 踩坑记（1）——路径体操" loading="lazy"/><div>https://musherm.github.io/nobelium/path-middle</div></div></div><div class="notion-bookmark-image"><img src="https://og-image-craigary.vercel.app/Next.js%20%E8%B8%A9%E5%9D%91%E8%AE%B0%EF%BC%881%EF%BC%89%E2%80%94%E2%80%94%E8%B7%AF%E5%BE%84%E4%BD%93%E6%93%8D.png?theme=dark&amp;md=1&amp;fontSize=125px&amp;images=https%3A%2F%2Fnobelium.vercel.app%2Flogo-for-dark-bg.svg" alt="Next.js 踩坑记（1）——路径体操" loading="lazy"/></div></a></div><div class="notion-text notion-block-12e535aeba0948ed9b4ffae7ae1b356d">这篇博客是在 <a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://www.notion.so">notion</a> 上完成写作的（包括现在这篇），再加之我有过了解，有很多项目支持直接从 notion 构建自己的个人博客，作为一个前端开发者，怎么能没有自己的博客呢？于是开始选型，辗转之下找到了这篇文章：</div><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-cad2a80668d44b40b9b55c2eadd6dcb4" href="https://emergencyexit.xyz/upgrade-blog"><div><div class="notion-bookmark-title">从 奇思乱想 到 妙想天开</div><div class="notion-bookmark-description">打怪→获得经验→升级⤴️</div><div class="notion-bookmark-link"><img src="https://emergencyexit.xyz/apple-touch-icon.png" alt="从 奇思乱想 到 妙想天开" loading="lazy"/><div>https://emergencyexit.xyz/upgrade-blog</div></div></div><div class="notion-bookmark-image"><img src="https://og-image-craigary.vercel.app/%E4%BB%8E%20%E5%A5%87%E6%80%9D%E4%B9%B1%E6%83%B3%20%E5%88%B0%20%E5%A6%99%E6%83%B3%E5%A4%A9%E5%BC%80.png?theme=dark&amp;md=1&amp;fontSize=125px&amp;images=https%3A%2F%2Fnobelium.vercel.app%2Flogo-for-dark-bg.svg" alt="从 奇思乱想 到 妙想天开" loading="lazy"/></div></a></div><div class="notion-text notion-block-254f1eb665964205b2eee0fefdd17653">作者对各种从 notion 生成博客的项目进行了调研，最终选用了 nobelium 这一项目：</div><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-4f0d1046a086401d9d355f47d8945edf" href="https://github.com/craigary/nobelium"><div><div class="notion-bookmark-title">GitHub - craigary/nobelium: A static blog build on top of Notion and NextJS, deployed on Vercel.</div><div class="notion-bookmark-description">A static blog build on top of Notion and NextJS, deployed on Vercel. - craigary/nobelium</div><div class="notion-bookmark-link"><img src="https://github.com/fluidicon.png" alt="GitHub - craigary/nobelium: A static blog build on top of Notion and NextJS, deployed on Vercel." loading="lazy"/><div>https://github.com/craigary/nobelium</div></div></div><div class="notion-bookmark-image"><img src="https://repository-images.githubusercontent.com/347565905/8cc03300-9313-11eb-9c18-8387e9ede56b" alt="GitHub - craigary/nobelium: A static blog build on top of Notion and NextJS, deployed on Vercel." loading="lazy"/></div></a></div><div class="notion-text notion-block-f436727745aa44e7978d157045523fff">好了，看来烦人的选型工作已经有人替我完成了，话不多说，开整！由于这个项目原型使用的是 Vercel 部署，但本着省钱的原则，作者和我都选择使用 Github Page 进行部署，这就要求我们将项目编译成静态页面，作者对此进行了一定的改造，所以本博客的部署实际上使用了作者改造的项目：</div><div class="notion-row"><a target="_blank" rel="noopener noreferrer" class="notion-bookmark notion-block-e9ccf41641ad4f73915c5caf0c0a208e" href="https://github.com/IMBlues/nobelium"><div><div class="notion-bookmark-title">GitHub - IMBlues/nobelium: 布鲁斯鱼的妙想天开</div><div class="notion-bookmark-description">布鲁斯鱼的妙想天开. Contribute to IMBlues/nobelium development by creating an account on GitHub.</div><div class="notion-bookmark-link"><img src="https://github.com/fluidicon.png" alt="GitHub - IMBlues/nobelium: 布鲁斯鱼的妙想天开" loading="lazy"/><div>https://github.com/IMBlues/nobelium</div></div></div><div class="notion-bookmark-image"><img src="https://opengraph.githubassets.com/8f1dd2de2684b8c115d5731226908ffc6b9fba0995d0a48ee3fc81057c200965/IMBlues/nobelium" alt="GitHub - IMBlues/nobelium: 布鲁斯鱼的妙想天开" loading="lazy"/></div></a></div><div class="notion-text notion-block-acb3272ff1da45a1a05ce806f95a880f">在根据教程进行了一番操作之后，我们又遇到了那个熟悉的问题：静态资源路径错误！</div><div class="notion-text notion-block-2bfea135c3b34fe8a7cc96a1a224e691">这是什么历史的循环？我因为出现静态资源路径错误，写了<a target="_blank" rel="noopener noreferrer" class="notion-link" href="https://musherm.github.io/nobelium/path-middle">一篇博客</a>专门介绍怎么解决这个问题，并且顺便部署一个博客把这篇文章放上去，但又在部署博客的过程中又遇到了静态资源报错。。。 😅</div><div class="notion-text notion-block-3a536b7571f843dda0ddb7d96c52d7af">但是没关系！很幸运，这又是一个 Nextjs 项目，而我又恰巧刚刚学会如何解决，这不是手拿把掐？开整！</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-ed92dcf050b54aab98c65bd0e8a3c853" data-id="ed92dcf050b54aab98c65bd0e8a3c853"><span><div id="ed92dcf050b54aab98c65bd0e8a3c853" class="notion-header-anchor"></div><a class="notion-hash-link" href="#ed92dcf050b54aab98c65bd0e8a3c853" title="开始解决问题"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">开始解决问题</span></span></h3><div class="notion-text notion-block-d2aa64eff2b4431f8adcad209b53acdc">根据经验，我把nextconfig改成了这样：</div><pre class="notion-code language-javascript"><code class="language-javascript"><span class="token keyword">const</span> isProd <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">===</span> <span class="token string">"production"</span><span class="token punctuation">;</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">basePath</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/nobelium"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token literal-property property">assetPrefix</span><span class="token operator">:</span> isProd <span class="token operator">?</span> <span class="token string">"/nobelium/"</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
  <span class="token literal-property property">webpack5</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
  <span class="token operator">...</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><div class="notion-text notion-block-0b2b3f53630542fdb0f6b888d19aa07c">主要就是加了个basePath和assetPath，跟我在 <!-- -->‣<!-- --> 这篇文章里提到的一样，还需要在编译的过程中提供一个NODE_ENV=’production’，就像这样：</div><pre class="notion-code language-yaml"><code class="language-yaml"><span class="token operator">-</span> name<span class="token operator">:</span> Build
  <span class="token literal-property property">run</span><span class="token operator">:</span> <span class="token operator">|</span>
    npm i <span class="token operator">--</span>registry https<span class="token operator">:</span><span class="token operator">/</span><span class="token operator">/</span>registry<span class="token punctuation">.</span>npmmirror<span class="token punctuation">.</span>com <span class="token operator">--</span>legacy<span class="token operator">-</span>peer<span class="token operator">-</span>deps <span class="token operator">-</span>d 
    yarn build
    yarn <span class="token keyword">export</span>
  <span class="token literal-property property">env</span><span class="token operator">:</span>
    <span class="token constant">NODE_ENV</span><span class="token operator">:</span> production</code></pre><div class="notion-text notion-block-e7ff4eb3239641789f092160f595ddd0">这一改，完了，Github 服务端编译不通过了，最致命的是，我在本地编译能通过，到底是哪一个环节出了问题？</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-e19ab4785daf4de49da18218c6e49e79" data-id="e19ab4785daf4de49da18218c6e49e79"><span><div id="e19ab4785daf4de49da18218c6e49e79" class="notion-header-anchor"></div><a class="notion-hash-link" href="#e19ab4785daf4de49da18218c6e49e79" title="问题定位"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">问题定位</span></span></h3><div class="notion-text notion-block-edb2844d9b3d427aa379876d0ffbbc0d">我决定通过分步骤更改的方式来测试哪一步造成了影响，最终定位在修改 NODE_ENV 这一环节，修改后仔细查看 build 错误信息如下：</div><pre class="notion-code language-yaml"><code class="language-yaml"><span class="token punctuation">.</span><span class="token operator">/</span>node_modules<span class="token operator">/</span>gitalk<span class="token operator">/</span>dist<span class="token operator">/</span>gitalk<span class="token punctuation">.</span>css<span class="token punctuation">.</span>webpack<span class="token punctuation">[</span>javascript<span class="token operator">/</span>auto<span class="token punctuation">]</span><span class="token operator">!=</span><span class="token operator">!</span><span class="token punctuation">.</span><span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">/</span>loaders<span class="token operator">/</span>css<span class="token operator">-</span>loader<span class="token operator">/</span>src<span class="token operator">/</span>index<span class="token punctuation">.</span>js<span class="token operator">??</span>ruleSet<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>rules<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">.</span>oneOf<span class="token punctuation">[</span><span class="token number">7</span><span class="token punctuation">]</span><span class="token punctuation">.</span>use<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token operator">!</span><span class="token punctuation">.</span><span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">/</span>loaders<span class="token operator">/</span>postcss<span class="token operator">-</span>loader<span class="token operator">/</span>src<span class="token operator">/</span>index<span class="token punctuation">.</span>js<span class="token operator">??</span>ruleSet<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>rules<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">.</span>oneOf<span class="token punctuation">[</span><span class="token number">7</span><span class="token punctuation">]</span><span class="token punctuation">.</span>use<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token operator">!</span><span class="token punctuation">.</span><span class="token operator">/</span>node_modules<span class="token operator">/</span>gitalk<span class="token operator">/</span>dist<span class="token operator">/</span>gitalk<span class="token punctuation">.</span>css
<span class="token literal-property property">Error</span><span class="token operator">:</span> Cannot find module <span class="token string">'tailwindcss'</span>
Require stack<span class="token operator">:</span>
<span class="token operator">-</span> <span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">/</span>config<span class="token operator">/</span>blocks<span class="token operator">/</span>css<span class="token operator">/</span>plugins<span class="token punctuation">.</span>js
<span class="token operator">-</span> <span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">/</span>config<span class="token operator">/</span>blocks<span class="token operator">/</span>css<span class="token operator">/</span>index<span class="token punctuation">.</span>js
<span class="token operator">-</span> <span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">/</span>config<span class="token operator">/</span>index<span class="token punctuation">.</span>js
<span class="token operator">-</span> <span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">-</span>config<span class="token punctuation">.</span>js
<span class="token operator">-</span> <span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>index<span class="token punctuation">.</span>js
<span class="token operator">-</span> <span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>cli<span class="token operator">/</span>next<span class="token operator">-</span>build<span class="token punctuation">.</span>js
<span class="token operator">-</span> <span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>bin<span class="token operator">/</span>next
    at Module<span class="token punctuation">.</span><span class="token function">_resolveFilename</span> <span class="token punctuation">(</span>node<span class="token operator">:</span>internal<span class="token operator">/</span>modules<span class="token operator">/</span>cjs<span class="token operator">/</span>loader<span class="token operator">:</span><span class="token number">1075</span><span class="token operator">:</span><span class="token number">15</span><span class="token punctuation">)</span>
    at mod<span class="token punctuation">.</span><span class="token function">_resolveFilename</span> <span class="token punctuation">(</span><span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">/</span>require<span class="token operator">-</span>hook<span class="token punctuation">.</span>js<span class="token operator">:</span><span class="token number">183</span><span class="token operator">:</span><span class="token number">28</span><span class="token punctuation">)</span>
    at Function<span class="token punctuation">.</span><span class="token function">resolve</span> <span class="token punctuation">(</span>node<span class="token operator">:</span>internal<span class="token operator">/</span>modules<span class="token operator">/</span>cjs<span class="token operator">/</span>helpers<span class="token operator">:</span><span class="token number">116</span><span class="token operator">:</span><span class="token number">19</span><span class="token punctuation">)</span>
    at <span class="token function">loadPlugin</span> <span class="token punctuation">(</span><span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">/</span>config<span class="token operator">/</span>blocks<span class="token operator">/</span>css<span class="token operator">/</span>plugins<span class="token punctuation">.</span>js<span class="token operator">:</span><span class="token number">49</span><span class="token operator">:</span><span class="token number">32</span><span class="token punctuation">)</span>
    at <span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">/</span>config<span class="token operator">/</span>blocks<span class="token operator">/</span>css<span class="token operator">/</span>plugins<span class="token punctuation">.</span>js<span class="token operator">:</span><span class="token number">162</span><span class="token operator">:</span><span class="token number">56</span>
    at Array<span class="token punctuation">.</span><span class="token function">map</span> <span class="token punctuation">(</span><span class="token operator">&lt;</span>anonymous<span class="token operator">></span><span class="token punctuation">)</span>
    at Object<span class="token punctuation">.</span><span class="token function">getPostCssPlugins</span> <span class="token punctuation">(</span><span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">/</span>config<span class="token operator">/</span>blocks<span class="token operator">/</span>css<span class="token operator">/</span>plugins<span class="token punctuation">.</span>js<span class="token operator">:</span><span class="token number">162</span><span class="token operator">:</span><span class="token number">47</span><span class="token punctuation">)</span>
    at async <span class="token operator">/</span>home<span class="token operator">/</span>runner<span class="token operator">/</span>work<span class="token operator">/</span>nobelium<span class="token operator">/</span>nobelium<span class="token operator">/</span>node_modules<span class="token operator">/</span>next<span class="token operator">/</span>dist<span class="token operator">/</span>build<span class="token operator">/</span>webpack<span class="token operator">/</span>config<span class="token operator">/</span>blocks<span class="token operator">/</span>css<span class="token operator">/</span>index<span class="token punctuation">.</span>js<span class="token operator">:</span><span class="token number">99</span><span class="token operator">:</span><span class="token number">36</span></code></pre><div class="notion-text notion-block-8fc7545325554bdd80c27e81157a844a">就是一堆模块找不到的问题，此时的我百思不得其解，我只是更改了一个环境变量，为什么会导致模块找不到呢？我把这个问题拿去问 GPT，GPT回复的答案仿佛醍醐灌顶，帮我彻底解决了这一问题，也就是本文的标题——<b>开发依赖。</b></div><div class="notion-text notion-block-ae90cdb0ed4042ca8853921298b6acaf">查看 package.json 可得：</div><pre class="notion-code language-json"><code class="language-json"><span class="token punctuation">{</span>
  <span class="token string-property property">"name"</span><span class="token operator">:</span> <span class="token string">"nobelium"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"version"</span><span class="token operator">:</span> <span class="token string">"1.3.0"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"homepage"</span><span class="token operator">:</span> <span class="token string">"https://nobelium.js.org"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"license"</span><span class="token operator">:</span> <span class="token string">"MIT"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"repository"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"type"</span><span class="token operator">:</span> <span class="token string">"git"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"url"</span><span class="token operator">:</span> <span class="token string">"https://github.com/craigary/nobelium.git"</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token string-property property">"author"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"name"</span><span class="token operator">:</span> <span class="token string">"Craig Hart"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"email"</span><span class="token operator">:</span> <span class="token string">"i@craigary.net"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"url"</span><span class="token operator">:</span> <span class="token string">"http://craigary.net"</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token string-property property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"dev"</span><span class="token operator">:</span> <span class="token string">"next dev"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"build"</span><span class="token operator">:</span> <span class="token string">"next build &amp;&amp; node ./.next/server/scripts/generate-rss.js"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"export"</span><span class="token operator">:</span> <span class="token string">"next export"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"start"</span><span class="token operator">:</span> <span class="token string">"next start"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"lint"</span><span class="token operator">:</span> <span class="token string">"next lint"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"postbuild"</span><span class="token operator">:</span> <span class="token string">"next-sitemap --config next-sitemap.config.js"</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token string-property property">"dependencies"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"axios"</span><span class="token operator">:</span> <span class="token string">">=0.21.1"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"feed"</span><span class="token operator">:</span> <span class="token string">"^4.2.2"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"gitalk"</span><span class="token operator">:</span> <span class="token string">"^1.7.2"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"next"</span><span class="token operator">:</span> <span class="token string">"^12.1.0"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"notion-client"</span><span class="token operator">:</span> <span class="token string">"^4.10.0"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"notion-utils"</span><span class="token operator">:</span> <span class="token string">"^4.10.0"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"preact"</span><span class="token operator">:</span> <span class="token string">"^10.5.15"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"react"</span><span class="token operator">:</span> <span class="token string">"^17.0.2"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"react-cusdis"</span><span class="token operator">:</span> <span class="token string">"^2.0.1"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"react-dom"</span><span class="token operator">:</span> <span class="token string">"^17.0.2"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"react-notion-x"</span><span class="token operator">:</span> <span class="token string">"^4.11.0"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"use-ackee"</span><span class="token operator">:</span> <span class="token string">"^3.0.0"</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token string-property property">"devDependencies"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"autoprefixer"</span><span class="token operator">:</span> <span class="token string">"^10.4.0"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"eslint"</span><span class="token operator">:</span> <span class="token string">"&lt;8.0.0"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"eslint-config-next"</span><span class="token operator">:</span> <span class="token string">"^12.0.3"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"eslint-config-standard"</span><span class="token operator">:</span> <span class="token string">"^16.0.2"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"eslint-plugin-import"</span><span class="token operator">:</span> <span class="token string">"^2.25.2"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"eslint-plugin-node"</span><span class="token operator">:</span> <span class="token string">"^11.1.0"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"eslint-plugin-promise"</span><span class="token operator">:</span> <span class="token string">"^5.1.1"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"eslint-plugin-react"</span><span class="token operator">:</span> <span class="token string">"^7.26.1"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"next-sitemap"</span><span class="token operator">:</span> <span class="token string">"^1.6.203"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"postcss"</span><span class="token operator">:</span> <span class="token string">"^8.3.11"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"tailwindcss"</span><span class="token operator">:</span> <span class="token string">"^2.2.19"</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token string-property property">"bugs"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"url"</span><span class="token operator">:</span> <span class="token string">"https://github.com/craigary/nobelium/issues"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"email"</span><span class="token operator">:</span> <span class="token string">"i@craigary.net"</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre><div class="notion-text notion-block-7d6de274281c4ef89df9606c601e0b84">我突然发现，原来报错里找不到的tailwindcss被标记成了开发依赖！当我在NODE_ENV里指定环境为生产环境时，就不会安装这个包，从而导致编译错误！解决的办法也很简单，把这几个包都挪到dependencies里就行了。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-bb1a9f7a85914b5eb271801883ba2e45" data-id="bb1a9f7a85914b5eb271801883ba2e45"><span><div id="bb1a9f7a85914b5eb271801883ba2e45" class="notion-header-anchor"></div><a class="notion-hash-link" href="#bb1a9f7a85914b5eb271801883ba2e45" title="总结"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">总结</span></span></h3><div class="notion-text notion-block-dc5197a3b06e4b3488a1da6bdc3ccf48">这次的问题主要还是拿来主义在作祟，本来项目就是傻瓜式的，我能拿来直接用，部署步骤里面也没写我要改package.json，我为什么要关注这个呢？所以我自然也不会知道有些东西被标记成了开发依赖（我在此之前甚至不知道有这个东西）</div><div class="notion-text notion-block-f54c30e5b673428693612ebf99692269">至于成果展示嘛，你现在看到的这篇博客，就是我部署的博客了，如果你看到这里，wish you have a good day!</div></main>]]></content:encoded>
        </item>
    </channel>
</rss>