<?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>愧怍</title>
        <link>https://kuizuo.me/blog</link>
        <description>feedId:41215011978385457+userId:41840354283324416</description>
        <lastBuildDate>Wed, 31 Dec 2025 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-CN</language>
        <copyright>Copyright © 2026 愧怍 Built with Docusaurus.&lt;p&gt;&lt;a href="http://beian.miit.gov.cn/" class="footer_lin"&gt;闽ICP备2020017848号-3&lt;/a&gt;&lt;/p&gt;</copyright>
        <item>
            <title><![CDATA[2025 · 在迷失中遇见]]></title>
            <link>https://kuizuo.me/blog/2025-year-end-summary</link>
            <guid>https://kuizuo.me/blog/2025-year-end-summary</guid>
            <pubDate>Wed, 31 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[这一年，我在失去与低谷中迷失，又在偶然的相遇里被拉回生活；虽然在工作与技术上退步，却开始学会为身体、选择与人生负责——2025 没有让我更成功，但让我更清醒地活着。]]></description>
            <content:encoded><![CDATA[<p>又开始急急忙忙地写 2025 年终总结了。</p>
<p>如果用一句话概括这一年，大概是：<strong>我把大多数时间花在了生活上，却把自己的技术和工作上弄丢了。</strong></p>
<p>但我找到了一个想要一起生活的人，在经历了失去、低谷、重启之后，才开始重新正视“我到底要过怎样的人生”。</p>
<p>p.s.有些事情直到写总结的时候才发现，原来已经过去了这么久，这么快。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="老家远程工作生活">老家：远程工作生活<a href="https://kuizuo.me/blog/2025-year-end-summary#%E8%80%81%E5%AE%B6%E8%BF%9C%E7%A8%8B%E5%B7%A5%E4%BD%9C%E7%94%9F%E6%B4%BB" class="hash-link" aria-label="老家：远程工作生活的直接链接" title="老家：远程工作生活的直接链接">​</a></h2>
<p>可能是由于在老家待了太久，有点腻了。</p>
<p>每天对着电脑，几乎没有任何社交；时不时的作息紊乱，昼夜颠倒。好的一点是，每天还能吃到老妈做的饭菜，但那更像是一种被照顾着“活着”，而不是在真正生活。</p>
<p>借着参加技术大会，我给自己安排了一趟江浙沪的旅行（事实是换了个地方酒店住了）。详细见：<a href="https://kuizuo.me/blog/special-forces-travel/" target="_blank" rel="noopener noreferrer"><strong>漫无目的的特种兵式旅行</strong></a></p>
<p>当时去旅行的感悟如下，现在回看这个任务我还真完成了。</p>
<blockquote>
<p>我不敢去想象一个人身处陌生城市、身边没有朋友的感觉。或许，如果身边能有一个人陪着一起旅行，一切都会变得不一样。嗯，看来是时候考虑找个对象了。</p>
</blockquote>
<p>旅行让我意识到自己太过渺小，这个世界还有太多我未见识过，未体验过的东西。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="十四第一次真正面对失去">十四：第一次真正面对“失去”<a href="https://kuizuo.me/blog/2025-year-end-summary#%E5%8D%81%E5%9B%9B%E7%AC%AC%E4%B8%80%E6%AC%A1%E7%9C%9F%E6%AD%A3%E9%9D%A2%E5%AF%B9%E5%A4%B1%E5%8E%BB" class="hash-link" aria-label="十四：第一次真正面对“失去”的直接链接" title="十四：第一次真正面对“失去”的直接链接">​</a></h2>
<p>今年年初，我养了一只蓝白猫，它是我花了 1400 买来的，故取名为十四。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/d845dc3875aade4f8917ce152239b21a.jpg" alt="IMG_1246.JPG" class="img_OpE3"></p>
<p>它陪了我四个月。那段时间它已然成了我生活的一部分，每次我在电脑前，它都会坐在机箱上，陪在我身旁。它或许不知道我在干嘛，但它只想无时无刻的跟着我。</p>
<p>但在一次误吞异物后，因为我的迟钝愚昧，我未能将它救活。</p>
<p>那几天我几乎是麻木的，自责、懊悔、否定自己。我脑子里一遍遍地回放当时的画面，无论脑海里怎么想，都无法改变结局。</p>
<p>善后是我妈处理的，她不告诉我猫被埋在哪里，还把跟猫咪有关的所有东西都丢了，不愿让我再看到想起。我知道我妈也舍不得，可毕竟我是她的儿子，还要来安慰我。作为过来人，她太明白离别的滋味。</p>
<p>当失去真正发生时，它不会给你任何缓冲的时间，只会直接落在你身上，让你第一次意识到：有些错误，是没有补救机会的；有些陪伴，一旦结束，就再也回不来了。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/1b4e30876dcb75d698f6ef4815ce8518.png" alt="image.png" class="img_OpE3"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="断联销声匿迹的一个月">断联：销声匿迹的一个月<a href="https://kuizuo.me/blog/2025-year-end-summary#%E6%96%AD%E8%81%94%E9%94%80%E5%A3%B0%E5%8C%BF%E8%BF%B9%E7%9A%84%E4%B8%80%E4%B8%AA%E6%9C%88" class="hash-link" aria-label="断联：销声匿迹的一个月的直接链接" title="断联：销声匿迹的一个月的直接链接">​</a></h2>
<p>之后的一段时间，我消失了将近一个月，我把自己”关“在房间里，切断了大多数外界的联系。我企图用游戏来麻痹，用重复和即时反馈来逃避现实，对生活逐渐失去了向往</p>
<p>那种状态我难以用言语形容——不是痛苦，而是<strong>什么都不想要了</strong>。</p>
<p>醒来、打游戏、再睡去，像是在维持最低限度的存在感。就这样持续了一个月之久，时间在往前走，而我只是被动地活着，仿佛“活着”也不再具备明确的意义。</p>
<p>直到 5 月 3 号的那个晚上。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="恋爱一次极其偶然的开始">恋爱：一次极其偶然的开始<a href="https://kuizuo.me/blog/2025-year-end-summary#%E6%81%8B%E7%88%B1%E4%B8%80%E6%AC%A1%E6%9E%81%E5%85%B6%E5%81%B6%E7%84%B6%E7%9A%84%E5%BC%80%E5%A7%8B" class="hash-link" aria-label="恋爱：一次极其偶然的开始的直接链接" title="恋爱：一次极其偶然的开始的直接链接">​</a></h2>
<p>那天是假期的晚上，我和一个大学同学在玩《英雄联盟》大乱斗模式。正好缺人，于是我们在 Wegame 的“人才市场”里随便拉了人。</p>
<p>她就这么“混”了进来。恋爱故事，也是在这样毫不起眼的契机下开始的。</p>
<p>那天晚上我们一路连胜，配合的堪称无与伦比。最终完美下播互加了好友，我当时并没多想，以为也就是一次普通的游戏搭子。</p>
<p>起初她还只是询问我的日常，我略感烦躁不愿搭理。但我能从对话与游戏中明显感到她对我有所好感，我曾一度不愿意恋爱，关系就这样僵持在游戏搭子上。当时的我差点将其拉黑，否则大概就错过她了。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/10ab3626d01a9484a0a367cb52f8523c.png" alt="image.png" class="img_OpE3"></p>
<p>后来才发现，我们都属于那种<strong>很聊得来、也很放得开</strong>的性格，说话直接，<strong>不喜欢拐弯抹角</strong>。随着聊天深入，得知她比我大几岁(姐弟恋)，从事硬件开发(我则是软件开发)。白天高强度上班，晚上还能打打游戏，周末还游山玩水的，我是完全看不出有宅女的模样。</p>
<p>我们之间还有太多相似之处：话题、节奏、对很多事情的理解方式，几乎不用刻意寻找共同点。尤其是在《英雄联盟》上。对背景故事和世界观的理解，我们几乎完全对齐。随便提一个英雄，我们基本都能立刻说出 ta 属于哪个地区；听到一句台词，也能马上反应出对应的是哪位英雄。</p>
<p>在一次深夜对话中，我们聊起各自的过去，从生活聊到情感，一直聊到天亮。这种感觉就如同找到了灵魂伴侣一般，我们就这样在一起了。</p>
<p>她后来形容这种默契时说过一句话：<strong>“你屁股一撅，我就知道你要放什么屁。”</strong></p>
<p>话糙，但准确。</p>
<p>我开始意识到：我要改变一下现状了。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="健身短暂但真实的自我重启">健身：短暂但真实的自我重启<a href="https://kuizuo.me/blog/2025-year-end-summary#%E5%81%A5%E8%BA%AB%E7%9F%AD%E6%9A%82%E4%BD%86%E7%9C%9F%E5%AE%9E%E7%9A%84%E8%87%AA%E6%88%91%E9%87%8D%E5%90%AF" class="hash-link" aria-label="健身：短暂但真实的自我重启的直接链接" title="健身：短暂但真实的自我重启的直接链接">​</a></h2>
<p>当时的我们还只是网恋，我转向镜子看了看我的身材，不由得摇起了头。</p>
<p>我整个家族的基因都是偏瘦型，我也一样，加之我对食物可以说没有任何欲望，常年体重维持在 50kg 徘徊，此时的我甚至只有 47kg。</p>
<p>为了“科学增重”，我开始了健身。</p>
<p>瘦子健身的新手保护期真不是盖的，仅仅 1 个月就从 47kg 干到 55kg，这在我以前是想都不敢想的。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/0477b3245d05091dded42d92e36828d3.jpg" alt="IMG_3304.jpg" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/0fab810a26329f0fac02ac2098880801.png" alt="IMG_4909.PNG" class="img_OpE3"></p>
<p>与之对应的”代价“是我一日 6 餐，除去一日正常 3 餐，外加上午茶、下午茶(喝增肌粉)和夜宵。而如果不健身，不去做无氧力量训练，我是无法食入这么多食物的。加上那段时间每天早睡早起，肌肉(体重)就在休息中慢慢长了上来。</p>
<p>我第一次真切地感受到：<strong>身体是可以被训练的，人生也是。(习惯的力量)</strong></p>
<p>可健身和学英语一样，难的从来不是开始，而是坚持😣。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="深圳工作与自我焦虑">深圳：工作与自我焦虑<a href="https://kuizuo.me/blog/2025-year-end-summary#%E6%B7%B1%E5%9C%B3%E5%B7%A5%E4%BD%9C%E4%B8%8E%E8%87%AA%E6%88%91%E7%84%A6%E8%99%91" class="hash-link" aria-label="深圳：工作与自我焦虑的直接链接" title="深圳：工作与自我焦虑的直接链接">​</a></h2>
<p>8 月 20 日我定了一张从福州到深圳的动车，接着就有了我在深圳 4 个月多的生活，这一块详见：<a href="https://kuizuo.me/blog/shenzhen-life" target="_blank" rel="noopener noreferrer"><strong>来深圳四个月的生活点滴</strong></a></p>
<p>来到深圳，一部分原因是为爱奔赴，另一部分原因，则是我想逼自己走出舒适区，真正具备<strong>独立生活的能力</strong>。从小到大，我几乎一直活在家人的保护之下，很多事情都有人兜底，也正因如此，我对很多现实问题始终缺乏足够的感知。</p>
<p>回过头看，今年我在工作上的表现，确实不称职。我没有拿出 2021 年那种孤注一掷、休学冲刺的状态。当时的我好像只用一个月，就完成了某种“自证”。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/ae0174899acd4f6bb8a8d19d7987cafe.png" alt="image.png" class="img_OpE3"></p>
<p>而今年，我似乎连开始的力气都没有。老板其实给过我不少机会，也留过余地。只是很多选择，走到最后，还是我自己做出的决定。</p>
<p>那段时间我反复思考一个问题：<strong>我到底适合怎样的工作环境？</strong></p>
<p>我始终觉得自己更适合创业型公司，节奏快、责任重、没有太多缓冲空间，但正是这种状态，反而能逼着我往前走，逼着我学习，也逼着我承担结果。相比之下，过于稳定、边界清晰的环境，反而容易让我松懈，甚至逃避。</p>
<p>也让我对自己的性格和状态有了阶段性的认识，如果没有外部压力，也没有清晰目标，我很容易停在原地，对“努力”本身失去感知。</p>
<p>修 bug 我比较在行，但我更需要 fix 我自己。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="冻结x-账号被封">冻结：X 账号被封<a href="https://kuizuo.me/blog/2025-year-end-summary#%E5%86%BB%E7%BB%93x-%E8%B4%A6%E5%8F%B7%E8%A2%AB%E5%B0%81" class="hash-link" aria-label="冻结：X 账号被封的直接链接" title="冻结：X 账号被封的直接链接">​</a></h2>
<p>我今年大部分的感想、分享都放到了 X 上，得知账号被封的那一刻我有点懵逼。意味着以后别人再也无法看到，而我自己自然也无法在原有的账号发表内容。</p>
<p>一开始我试图申诉找回，但是最终无果，我就想那就算了，“不过是一个账号而已。”我当时并不在意。</p>
<p>但冷静下来之后才发现，在互联网世界里，<strong>个人 IP 和持续输出，本身就是一种生存方式</strong>。而从平台属性和传播效率来看，X 是目前最合适的选择之一。</p>
<p>同时推上也莫名开始兴起一批起号潮，好像一时间所有的人都在 X 创作内容。于是我重新起号，也顺势回顾了一遍自己过去的“起号经历”，一开始只是分享经历、生活，内容也并非精挑细选，真正起作用的是引起共鸣，是我用一种独特的表达方式来呈现。</p>
<p>某种程度上，账号被封也算一次提纯。粉丝少了，围观的人少了，我不再需要顾虑太多目光，我似乎又可以像一开始那样直言不讳，可现在我又不敢了。</p>
<p>不为流量而活，其实也挺好的。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="杂项那些微小却真实的快乐">杂项：那些微小却真实的快乐<a href="https://kuizuo.me/blog/2025-year-end-summary#%E6%9D%82%E9%A1%B9%E9%82%A3%E4%BA%9B%E5%BE%AE%E5%B0%8F%E5%8D%B4%E7%9C%9F%E5%AE%9E%E7%9A%84%E5%BF%AB%E4%B9%90" class="hash-link" aria-label="杂项：那些微小却真实的快乐的直接链接" title="杂项：那些微小却真实的快乐的直接链接">​</a></h2>
<p>游戏机：今年买了 Switch 2，也算是圆了小时候一直想要游戏机的梦。顺便把梦寐以求的《塞尔达：旷野之息》通关了。虽然后期其实玩得有点急，更多像是想尽快结束，好让自己不用再被它分心。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/83fda6b75c08b11aec0e1d3faa04e8f3.jpg" alt="IMG_4888.jpg" class="img_OpE3"></p>
<p>露营：今年去露了一次营，没有什么精致装备，也谈不上多专业，只是把帐篷搭起来，把生活从室内搬到户外。吹着海风，享受着食物，看着天色慢慢变化，在车流高峰到来之前收拾好东西回家。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/69173090a548ae4fdd6ed483c87fcce6.heic" alt="IMG_3557.HEIC" class="img_OpE3"></p>
<p>泡了个温泉： 就只是单纯去泡了个温泉，没想到如此惬意。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/2ab5b27e1717a33a08499982f6747a93.jpg" alt="IMG_6335.JPG" class="img_OpE3"></p>
<p>出国凭证：今年拿了港澳通行证和护照，意味着我终于拥有了走出去的可能，哪怕暂时还没有明确的计划，但这种“随时可以离开”的感觉，是我之前从未有过的安心。（深圳办证效率是真快，好评）</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="明年我想这样活着">明年，我想这样活着<a href="https://kuizuo.me/blog/2025-year-end-summary#%E6%98%8E%E5%B9%B4%E6%88%91%E6%83%B3%E8%BF%99%E6%A0%B7%E6%B4%BB%E7%9D%80" class="hash-link" aria-label="明年，我想这样活着的直接链接" title="明年，我想这样活着的直接链接">​</a></h2>
<p>或许如果没有今年恋爱的开始，我仍会迷茫，不知道此刻的自己该何去何从——是否还在逃避现实，是否能真正重获新生。</p>
<p>总的来说，今年对我而言仍然充满了起伏。</p>
<p>我不得不承认：
我依旧很难权衡工作与爱情，有时像是一个被爱冲昏头脑的人。</p>
<p>她常说，我总是把未来幻想得很美好，但行动总是慢半拍。</p>
<p>明年，我希望自己能做几件具体的事：</p>
<ul>
<li>给自己更多独处和学习的时间</li>
<li>重拾健身计划，让身体和精神都更有力量</li>
<li>补上“哑巴英语”，她要考雅思，我也不能太拉</li>
<li>找到一个可持续的收入来源，给生活更多安全感</li>
</ul>
<p>今年，有人陪我一起过年了。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>年终总结</category>
            <category>生活</category>
            <category>情感</category>
        </item>
        <item>
            <title><![CDATA[来深圳四个月的生活点滴]]></title>
            <link>https://kuizuo.me/blog/shenzhen-life</link>
            <guid>https://kuizuo.me/blog/shenzhen-life</guid>
            <pubDate>Tue, 23 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[作者以“为爱奔赴”为起点，串联起恋爱、离职、投机失控与社交平台失去等经历，记录了情感牵引与现实冲击中逐步失去掌控、又重新审视自我与生活方向的过程，整体呈现出一种从投入、迷失到清醒反思的阶段性生命状态。]]></description>
            <content:encoded><![CDATA[<p>一眨眼，今年就只剩下不到一周了，而我来到深圳，也已经接近第五个月。</p>
<p>想给这段时间做一个稍微完整一点的记录。与其说是讲故事，不如说是整理这几个月里生活感受上的变化——不会事无巨细地还原来龙去脉，只记录那些真实发生在我身上的状态转变。</p>
<p>从为爱奔赴来到深圳→开始一段全新的生活→到远程工作离职→再到接触合约交易、经历爆仓→X 账号被封→带对象回老家参加亲戚婚礼。</p>
<p>这篇文章其实上个月就已经在脑子里打过草稿，却一直拖到现在。很多具体的事情，或许之后还会单独写成几篇慢慢展开，但至少现在，想先给当下留下一份备份。</p>
<p>也算是，给这四个月一个暂时的落点。</p>
<hr>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="一来深圳为爱奔赴">一、来深圳——为爱奔赴<a href="https://kuizuo.me/blog/shenzhen-life#%E4%B8%80%E6%9D%A5%E6%B7%B1%E5%9C%B3%E4%B8%BA%E7%88%B1%E5%A5%94%E8%B5%B4" class="hash-link" aria-label="一、来深圳——为爱奔赴的直接链接" title="一、来深圳——为爱奔赴的直接链接">​</a></h2>
<p>去年一毕业就顺从「家里人的安排」下在福州生活，后来因为远程工作，又回到老家的县城待了半年多。那段时间谈不上好或坏，只是节奏稳定、路径清晰，一切都在既定轨道上往前走。而我家的猫因为我的愚昧而逝世。那件事发生之后，我很长一段时间都没缓过来。不是那种歇斯底里的难过，而是一种持续的、钝钝的自责，迟迟无法走出。</p>
<p>就这样在老家整天沉迷游戏，直到一次游戏中遇到我现在的对象。起初只是很随意的交流，从游戏里的配合，到语音里的闲聊，再到慢慢延伸到生活本身。我发现我们出奇地聊得来，无论是三观还是兴趣，都有着难得的契合感。那种感觉很难形容，好像我们就是天生一对！</p>
<p>于是我开始谈恋爱并网恋奔现。第一次来到她所在的城市，我们一起坐上了「湾区之光」摩天轮，开始续写我与她的故事。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/4373932e66b0d1005a8c655c6dfdad1b.png" alt="image.png" class="img_OpE3"></p>
<p>当然，事情并不总是一帆风顺。
在一次分别之后，我们因为一些不和分开了一个月。但好在，最终我们还是选择了彼此，重新走到了一起。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/098e8393881a8ed1e4ce62444237b129.png" alt="image.png" class="img_OpE3"></p>
<p>复合后不久，我便搬到了深圳，也真正开始了这段恋爱生活。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="二生活环境与消费">二、生活环境与消费<a href="https://kuizuo.me/blog/shenzhen-life#%E4%BA%8C%E7%94%9F%E6%B4%BB%E7%8E%AF%E5%A2%83%E4%B8%8E%E6%B6%88%E8%B4%B9" class="hash-link" aria-label="二、生活环境与消费的直接链接" title="二、生活环境与消费的直接链接">​</a></h2>
<p>这次选择来到深圳，算是我人生第一次真正意义上的「自我安排」：一个人离开原本熟悉的城市，脱离家人视线与既有环境，重新开始一套属于自己的生活节奏。</p>
<p>关于租房，我先把预算和基本要求告诉了对象，她提前帮我跑了几家中介，筛选了几个房源让我选。最终定下的是一套离她家大概 200 米左右的公寓，租期半年。这也意味着，只要时间允许，接下来的生活里我们几乎可以天天见面。</p>
<p>在正式入住前，我提前在网购平台买了一些生活用品和基础家居，等房子一交付，就一点点布置起来。当所有东西就位，有了一个可以安心睡觉、可以摊开电脑工作的地方之后，我突然意识到——
其实生活的“形态”，和我之前并没有本质区别。同样是一张桌子、一把椅子、一台电脑。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/297bcc5b5e0c369dea62e8ec812ad864.png" alt="image.png" class="img_OpE3"></p>
<p>消费上，明显能感觉到自己“添置了不少新东西”：</p>
<ul>
<li>Switch 2</li>
<li>Go Ultra 运动相机</li>
<li>投影仪</li>
<li>电动车</li>
<li>以及一些乱七八糟的小物件</li>
</ul>
<p>饮食上，早上麦当劳，中午快餐外卖，晚上和对象去吃堂食（有一段时间去她家吃她妈做的饭菜）。</p>
<p>直到每个月交房租的那几天，这些消费才会以一种非常现实的方式提醒我：<strong>在深圳生活，成本并不低。</strong></p>
<p>尤其是在两个人的生活模式下，“买给我们”的界限常常会变得模糊。一顿饭、一次出行、一个看似顺手的小决定，钱就这样在不经意间慢慢流走。</p>
<p>再加上一些冲动消费，让我对「理财」这件事的感受也慢慢变了。(见后文)</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="三恋爱的滋味">三、恋爱的滋味<a href="https://kuizuo.me/blog/shenzhen-life#%E4%B8%89%E6%81%8B%E7%88%B1%E7%9A%84%E6%BB%8B%E5%91%B3" class="hash-link" aria-label="三、恋爱的滋味的直接链接" title="三、恋爱的滋味的直接链接">​</a></h2>
<p><strong>我原本是那种很习惯独来独往的人。</strong></p>
<p>一个人待着不会觉得无聊，反而有点享受 <em>Alone</em> 的状态：自己吃饭、自己走路、自己消磨时间，不需要解释，也不需要配合。</p>
<p>但自从有了对象之后，生活里开始频繁出现“我们”这个词。
一起吃饭、一起逛街、一起出门遛狗散步、一起吐槽每天发生的琐事——
明明都是些再普通不过的日常，却总能聊出一堆废话，而且怎么都讲不完。</p>
<p>恋爱不到半年，我们却已经一起经历了很多事，其中不少都被我零零散散地记录在 X 上。回头看才发现，这段关系里充满了幸福。</p>
<ul>
<li>一起加入了网易云双人空间开始一起听</li>
<li>创建了 iCloud 的”家庭共享”，并将当前的片刻放在共享相簿里</li>
<li>双方都给对方开通了微信的亲属卡</li>
<li>一起去海边露营，吹海风</li>
<li>给她注册了一个域名——[她英雄联盟 ID].lol</li>
<li>去她家吃她妈妈亲手做的晚饭。</li>
<li>带她一起回老家参加我表哥的婚礼</li>
<li>……</li>
</ul>
<p>这些事情单独拿出来看，其实都不算多么特别。
可当它们被串联在一起的时候，就变成了一种只属于我们的轨迹。</p>
<p>当然，恋爱并不全是甜的。</p>
<p><strong>恋爱时的大部分行为就不能自作主张了</strong>，而我偏偏又是那种习惯凭感觉做决定的人。有一次我们吵架，她说我经常临时更改计划，却很少提前和她商量，也没有真正站在她的角度考虑问题。那一刻我才意识到，自己某些看似“随性”的行为，本质上其实挺自私的。</p>
<p>恋爱带给我最明显的变化之一，是<strong>属于自己的时间变少了</strong>。也可能是因为还处在热恋期，我们几乎天天约会、吃饭、见面。有时候我甚至会想：要是我真的是时间管理大师就好了(bushi)</p>
<p>钱包的变化倒没有那么强烈的痛感，但数字确实在一点点变少。不过快乐也是真的、很具体地在心里发生着。某种程度上来说，钱确实可以买到快乐。</p>
<p>**恋爱也让我变得更加难以专注。**有时候为了秒回对象的一条消息，就会不自觉地分神；我又不太愿意开勿扰模式，生怕错过什么。甚至偶尔刚进入心流状态，就被一条消息拉了出来。</p>
<p>果然，女人只会影响我写代码的速度。（🐶保命）</p>
<p>但即便如此，心里有一个人可以挂念，同时也被人挂念着，这种感觉本身，还是很幸福的。</p>
<hr>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="四远程工作离职">四、远程工作离职<a href="https://kuizuo.me/blog/shenzhen-life#%E5%9B%9B%E8%BF%9C%E7%A8%8B%E5%B7%A5%E4%BD%9C%E7%A6%BB%E8%81%8C" class="hash-link" aria-label="四、远程工作离职的直接链接" title="四、远程工作离职的直接链接">​</a></h2>
<p>10 月中旬，我从上一份远程工作中离职了。
不少人对此感到困惑——薪资不低、学习资源充足，工作内容也谈不上完全无法胜任。说实话，连我自己也曾反复问过这个问题。
那是一家初创公司，老板并非技术出身。技术选型往往来源于他的调研结果、个人偏好，以及社区里的风向反馈。对技术人员来说，这意味着你不仅要把事情做完，还要反复解释、沟通和说服：为什么不用某个框架、为什么这样设计更合理、替代方案又是什么。</p>
<p>项目没有明确的 KPI，也几乎没有严格的交付期限（当然，有些任务本身确实难以精确预估）。会议节奏也相对随意：有时因为老板临时有事而推迟，有时甚至直接顺延到第二天。表面看起来相当松弛，但人往往会在这种环境中，悄无声息地滋生惰性。</p>
<p>理论上，这样的模式并非不可行，但前提是团队成员必须具备极强的自我驱动力。缺乏外部约束的情况下，这种状态其实很难长期维持。团队需要的是高度自律，而不是网传的那种“随心所欲的自由”。</p>
<p>而现实中的对应却是：每一次周会，分享的内容看起来不少，讨论也算热烈，但项目本身始终没有实质性的推进。那种“好像很忙，却什么都没真正完成”的感觉，逐渐变得无法忽视。团队更在意的是有没有参与讨论、有没有在会议中碰撞出想法，而不是事情最终是否真的被做好。整个节奏像是“不急不躁、慢工出细活”，但久而久之，却让人看不到明确的方向和结果。</p>
<p>从工作当中，我也逐渐意识到，我并不擅长、也并不享受“说服他人”这件事。加之我本身算不上自律，尤其在编码和工作时间上极不规律——更多是靠灵感驱动，来了就一股脑儿输出，没来时效率几乎为零。</p>
<p>再后来，我发现自己已经不再像刚入职时那样投入。对工作的厌恶感慢慢滋生，拖延、逃避也随之出现。</p>
<p>尤其是在远程、没有人真正盯着你的情况下，我很清楚地意识到一件事：<strong>我在混</strong>。</p>
<p>不是能力不足，而是缺乏那种被推动、被逼着往前走的状态。</p>
<p>最终真正触发离职的，是国庆第一天（那天仍是工作日）。我白天和对象出去玩，却没有提前报备请假，被老板指出了问题。老板说得并没有错——一个人的下意识行为、选择和态度，往往比语言本身更诚实。</p>
<p>于是，我做出了离职的决定。（下方为当时的聊天记录）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/9d8c99d5d1869fc1d6e8b5ad9eef4ef7.png" alt="image.png" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/c4cd98db2e33fd09a32bd67dc73477ad.png" alt="image.png" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/584cd52a963be47440da8d928c0f625a.png" alt="image.png" class="img_OpE3"></p>
<p>尽管如此，我依然感谢这段经历。给了我一次工作机会，也让我更早看清了一些并不适合自己的东西。</p>
<p>至少有件事现在是确定的：事业与亲密关系之间，很难在短时间内做到真正兼得。</p>
<p>至于未来要怎么走，是否重新选择上班、重新磨炼自己——说实话，我暂时还给不出一个明确的答案。</p>
<hr>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="五沉迷合约交易">五、沉迷合约交易<a href="https://kuizuo.me/blog/shenzhen-life#%E4%BA%94%E6%B2%89%E8%BF%B7%E5%90%88%E7%BA%A6%E4%BA%A4%E6%98%93" class="hash-link" aria-label="五、沉迷合约交易的直接链接" title="五、沉迷合约交易的直接链接">​</a></h2>
<p>在我离职期间，正好经历了 <a href="https://www.binance.com/zh-CN/square/post/30860698949970" target="_blank" rel="noopener noreferrer">1011 黑天鹤爆跌</a>，我是这时候才开始正式留意币圈（虽然之前已经创建过账号了）。最初只是抱着“试试”的心态，接触加密货币和区块链开发。后来无意间刷到一篇推文，一时兴起，便开始照着模仿。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/fb5cfc76e06d915a1befe1ed47e21eb6.png" alt="image.png" class="img_OpE3"></p>
<p>我先从刷空投开始，主要是一些 Alpha 项目。你可以简单理解为：项目方通过“免费发币”来拉新、做宣传，而用户则需要通过刷交易量、交互成本来换取资格。实际收益并不高，确实只能赚点饭钱。</p>
<p>随着对交易所的使用逐渐熟悉，我开始注意到“合约”这个东西。起初并没有急着下场——说白了，我根本不懂交易，看不懂 K 线，也不了解消息面。但币圈的故事总是极端：有人一夜暴富，也有人一夜归零。尤其是 10·11 之后，这样的消息不断涌入视野。</p>
<p>对该“玩法”的介绍，推荐你观看该视频 <a href="https://www.notion.so/258c852983c98052a134f738ea275be4?pvs=21" target="_blank" rel="noopener noreferrer"><strong>从天堂到天台，为什么币圈总在爆仓？</strong></a></p>
<p>起初，我将一部分资金转入了合约账户，另一部分则留在现货里，用来继续刷 Alpha 积分。我严格遵循着低仓位，低杠杆，行情一走对，我便尝到了久违的“甜口”，账户数字稳步上扬，但我看着账户里那点并不惊艳的收益。于是，“试试加一点仓位”这个念头悄悄冒了出来。再然后是“稍微提点杠杆也没事”。很快，我已经从“试试”进化到了“再来一单”。</p>
<p>就这样交易频率开始失控，开仓、平仓、反手、再反手，就在我自我感觉良好、小有成就的时候。那天凌晨 3 点鲍威尔讲话来了，我不记得他那时开头讲的是 Good afternoon 还是 Hello everyone，只记得那时我选择做多，几秒之内，一根 K 线直插而下。盈利瞬间变成亏损，而且还在不断扩大。</p>
<p>我没有设置止损，只能眼睁睁地看着余额一点一点减少，仿佛在看一支正在缓慢放血的股票——<strong>每一秒都在掉血，每一秒都舍不得卖。</strong></p>
<p>在某个瞬间，我选择割肉离场。事后证明，这是个正确的决定，行情随后跌得更猛。这是我第一次正面感受到这种级别的市场冲击，也是在那一刻，我开始恐慌，不敢轻易操作，选择观望，此前我从未经历过这种级别的行情冲击，没想到第一次，就来得如此刺激、如此直接。</p>
<p>这时我还没意识到这个市场**远比我想象的要残酷得多。**而这次没有爆仓，命运似乎要给我一个更深刻的教训。</p>
<p>那天之后，我整个人好像进入了一种奇怪的状态。开始复盘，要是我当时手速快一点平仓然后反手梭哈做空，会不会已经让我的本金翻倍。我开始学习所谓的“正确交易”：<strong>止损、止盈、仓位管理。</strong></p>
<p>可随后的行情远比我想象的还要残酷，眼睁睁看着 BTC 从 107000 一路跌到 80000 出头，没有暴跌，没有黑天鹅，只是温水煮青蛙般的下行。这期间不断被打损，一开始所赚取的盈利一点点吐回出去。</p>
<p>这时的我已经开始有些急了，纪律开始松动。中间有几次明明达到了止损位，可我却不甘心就这样亏损，幻想着能够涨回去，就一直处于“套牢”，最后要么博一个回调卖了，要么下跌狠了直接割肉。这时的我开始失去了纪律，并没有严格执行策略，同时心想着要快点做一单赶快回本，就这样时不时盯盘寻找机会。</p>
<p>与此同时手上的筹码并不多了，一个更危险的念头出现在我的脑海里——我把原本用于现货定投和刷 Alpha 的资金，全部转进了合约账户。</p>
<p>赌徒心理，彻底接管了我。此刻我的内心只想着回本。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/d5db570f8ab2dff1914fcb68d4d5bb62.png" alt="image.png" class="img_OpE3"></p>
<p>11 月 21 日，BTC 探到 86000。那一刻我没有狂喜，只是松了一口气。随后出现反弹，账户曲线再次抬头。我开始变得“谨慎”，却又“犹豫”中错失机会。脑海里全是 “当时要是开仓多好”，就这样我又开始重新相信感觉。不再完全依赖止损，而是“灵活处理”；不再严格执行计划，而是“根据盘面优化”；每一次破坏纪律，都能找到一个听起来很专业的理由。</p>
<p>可账户已经不允许我再慢慢等了。行情稍微一动，心跳就开始加速。我发现自己已经不是在交易，而是在<strong>赌、在守</strong>——赌一根迟迟不来的反弹，守一段只存在于想象中的希望。</p>
<p>就这样我本以为行情会慢慢好起来，直到 12 月 1 日，一次极端波动，彻底终结了我的账户。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/634b8f5ef9b27d2a606ace47762df2d8.png" alt="image.png" class="img_OpE3"></p>
<p>我原本是有止损的，在那个点位，我本应果断离场。但就在最后一刻，我亲手把止损撤掉了。抱着一点点侥幸心理，妄想着硬撑到死。结果毫无悬念：仓位没了，撑不到第二天“迟到”的超跌大反弹。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/6eb5d791a64661809d0e20599a7dbd38.png" alt="image.png" class="img_OpE3"></p>
<p>我盯着清空的账户发了很久的呆，没有愤怒，也没有不甘，更多的是一种迟来的疲惫。我甚至不敢再做任何操作。不是因为没钱，而是因为我已经分不清——<strong>我再充进去的每一分钱，究竟是在参与交易，还是在给自己的侥幸续命。</strong></p>
<p>冷静下来后，我反而庆幸这次爆仓。它像一记重锤，把我从麻木和幻想中敲醒——让我终于脱离那条不断刷新、却早已失真的资金曲线，脱离对每一根 K 线的过度解读，脱离把情绪寄托在价格涨跌上的生活。</p>
<p>当我把这段经历写下来、拆解、复盘，我开始看清一些此前刻意回避的问题：</p>
<ul>
<li>我早已被困在币圈的信息茧房里</li>
<li>耐心被不断消耗，心态越来越浮躁</li>
<li>很多“策略”，本质上只是情绪决策</li>
<li>而最致命的，是我正在失去独立思考的能力</li>
</ul>
<p>每天醒来第一件事是看账户，睡前最后一件事也是看账户；隔一会儿就点进去刷一眼净值变化，盯着 K 线，看点位、等消息、猜讲话、纠结要不要挂一单。</p>
<p>表面上看，我好像在“认真交易、认真研究行情”，可回头一看，很多时候我什么正事都没做成。一天结束，除了情绪的反复拉扯，几乎没有任何真正沉淀下来的东西。</p>
<p><strong>健身计划被打断，生活作息被打乱，注意力被彻底撕碎</strong>。这其中或许也夹杂着我内心深处懒惰成性的借口。</p>
<p>而最关键的是，我正在一点点失去独立思考的能力。不是因为我有多懂交易，也不是因为我足够理性，而是我在一个自己并不真正理解、也完全没准备好的领域里，用真金白银去赌情绪。</p>
<p>但无论如何，这段经历至少让我清楚地看到了一件事——当一个人把全部精力都交给市场时，失去的往往不是钱，而是学习能力、生活节奏，以及对自己的掌控感。</p>
<p>这不是一个关于亏损的故事，而是一段关于<strong>失控</strong>的记录。</p>
<div class="theme-admonition theme-admonition-note admonition_fh9h alert alert--secondary"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>补充</div><div class="admonitionContent_oz3Y"><p>在沉迷币圈交易的那段时间里，我每天都在定投 <strong>100 美元的纳指</strong>。不看盘，不择时。也不用时时刻刻盯盘，每天看两眼就关。
它几乎不占用我的注意力，也不消耗情绪，更不需要我去证明自己“判断正确”。</p><p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/37253d8dfa96a673b97abbecaa47c450.png" alt="IMG_6378.PNG" class="img_OpE3"></p><p>或许，真正适合大多数人的投资方式，本就不该建立在判断力、手速和情绪之上。
而这一点，是在我经历了一整圈折腾之后，才慢慢意识到的。</p></div></div>
<hr>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="六推特被封">六、推特被封<a href="https://kuizuo.me/blog/shenzhen-life#%E5%85%AD%E6%8E%A8%E7%89%B9%E8%A2%AB%E5%B0%81" class="hash-link" aria-label="六、推特被封的直接链接" title="六、推特被封的直接链接">​</a></h2>
<p>12 月 11 号的早上 9 点，我还在像往常一样刷着推特，顺手把一则推文通过私信分享给对象。那时一切都很正常，时间线在刷新，仿佛世界从未停转。直到 10 点整，我的邮箱弹出了一封通知。是一封来自 X 的官方邮件——<strong>由于违反 X 规则，你的账号已被举报并被冻结。</strong></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/53c21c984462f4abf88ea0f62f4f06a9.png" alt="image.png" class="img_OpE3"></p>
<p>我盯着那行字看了很久。第一反应并不是愤怒，而是困惑：<strong>我违反了什么规则？</strong></p>
<p>我没有发过极端言论，也没做过垃圾广告，更谈不上引战。账号里大多数内容，不过是生活类的分享，个人吐槽，甚至连“输出观点”都算不上。</p>
<p>隔了一会儿，我才发现事情并不只是发生在我一个人身上。时间线里开始陆续出现各种**“账号没了”**的截图，</p>
<p>有人还在调侃，有人已经开始骂平台，也有人冷静地统计着被封账号的共同点。我则开始复盘自己最近的行为，并把这些猜测整理后，发布在新创建的小号上。</p>
<div>Loading...</div>
<p>这件事让我不由得想起本人 2019 年4-5月的一次经历。那年正值 QQ 的封号潮，我作为某个群的群主，因为群成员发布了违规内容，整个群连同我的账号一起被冻结。那也是我后来再也不愿意当群主的原因。</p>
<p>或许正因为有过类似的经历，如今再面对一个社交平台上的永久封号，情绪反倒没有想象中那么剧烈。震惊有之，遗憾有之，但更多的，竟是一种近乎麻木的熟悉感。好像这类事情，早就被我在心里预演过不止一次。</p>
<p>不过换个角度想，也未必全是坏事。</p>
<p>账号清零之后，我反而重新回到了一个无需顾虑粉丝、不必迎合算法的状态。
我本来就是为了记录而写，
而不是为了被看见才记录。</p>
<p>只是可惜的是，到了 2025 年，我才发现自己在推特上留下了那么多零散的文字、思考与片段，如今却只能自己查看，像被锁进了一个只对我开放的旧抽屉。（我会考虑做爬取我账号的信息做成一个归档页）</p>
<p>我又回头看了一眼自己的 blog——那个真正意义上归属权完全在我手里的地方。才发现今天的大多数记录，都被我习惯性地碎片化丢进了推特的时间线里；而自己的 blog，这一年却只更新了寥寥两篇。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/ba2843cd7868714e050616a1da6fdf1b.png" alt="image.png" class="img_OpE3"></p>
<p>未来我要养成在 Apple Note 上编写推文，然后在发布到 X 上的习惯，这样有助于我归档，以及防止类似的事情再次发生。</p>
<p>这次封号与其说是失去，不如说是一种提醒。提醒我该把笔重新拿回自己手里，把那些原本就该沉淀下来的文字，安放在一个不会被随时清空的地方。</p>
<p>或许，这才是我真正该回去的地方。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="七带对象回老家参加表哥婚礼">七、带对象回老家参加表哥婚礼<a href="https://kuizuo.me/blog/shenzhen-life#%E4%B8%83%E5%B8%A6%E5%AF%B9%E8%B1%A1%E5%9B%9E%E8%80%81%E5%AE%B6%E5%8F%82%E5%8A%A0%E8%A1%A8%E5%93%A5%E5%A9%9A%E7%A4%BC" class="hash-link" aria-label="七、带对象回老家参加表哥婚礼的直接链接" title="七、带对象回老家参加表哥婚礼的直接链接">​</a></h2>
<p>12 月中旬，我原本计划带着对象回老家参加表哥的婚礼，也算是第一次正式把她带进我的家庭与亲戚的视野里。不过婚礼安排在周三，而她当天下午还有一场英语演讲比赛，时间上实在无法兼顾，最终决定我周一回去，她在周四中午再抵达老家。</p>
<div>Loading...</div>
<p>我准备了一束花去接她，也是我第一次送真花给她(上次送的是积木花)。</p>
<p>在我那地方的风俗，女方第一次来到男方的家里长辈是要给见面礼的，于是我爸妈就带着她去金店挑选了一款手链给她。我的老家以海鲜出名，于是那几天的生活几乎被海鲜排挡承包，我和她甚至一起吃出了肚子轻微痉挛的感觉。</p>
<div>Loading...</div>
<p>我们还一起去夹娃娃、泡温泉。从她的表情上我不难看出，她对我的老家很满意。今年护照还到了，或许下一次记录的，就是我们第一次一起出国旅行了。</p>
<hr>
<p>嗯，就先写到这里吧。</p>
<p>这一年也快要结束了，至于未来的打算，等到年终，再好好梳理也不迟。</p>
<p>不过感觉这篇幅就像是 2025 年终总结了。。。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>经历</category>
            <category>恋爱</category>
            <category>生活</category>
            <category>反思</category>
        </item>
        <item>
            <title><![CDATA[第一次赴港记]]></title>
            <link>https://kuizuo.me/blog/first-trip-to-Hong-Kong</link>
            <guid>https://kuizuo.me/blog/first-trip-to-Hong-Kong</guid>
            <pubDate>Tue, 30 Sep 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[前几年由于某种原因，迟迟无法办理港澳通行证，因此也无法去往香港。我一直本以为我还要过些年才能赴港，没曾想到这一天竟如此快到来。]]></description>
            <content:encoded><![CDATA[<p>前几年由于某种原因，迟迟无法办理港澳通行证，因此也无法去往香港。我一直本以为我还要过些年才能赴港，没曾想到这一天竟如此快到来。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="办证">办证<a href="https://kuizuo.me/blog/first-trip-to-Hong-Kong#%E5%8A%9E%E8%AF%81" class="hash-link" aria-label="办证的直接链接" title="办证的直接链接">​</a></h2>
<p>其实我一开始并没那么想去办理港澳通行证。在此之前我一共去了3次出入境大厅，都不予通过，给我内心造成了不小的打击，心里总担心下一次还是同样的结果。</p>
<p>就在月初，我对象计划去香港（是的，我有对象了），问我是否同行。我说自己连港澳通行证都没有。她告诉我深圳办理很快就能拿到，还安慰我说："凡事往最坏处想，结果才能更容易接受。就算失败也无妨，万一成功了就是惊喜。"</p>
<p>于是我抱着"死马当活马医"的心态，又一次尝试港澳通行证申请。</p>
<p>果不其然，办证人员询问我上次办理为什么不予通过(有申请记录)，我简单叙述了下，他也没过问，由于我是外省户口，因此她还需要给我户口所在地发个回执核实下真实情况。</p>
<p>尽管等待的几天去查看办理进度时我还是胆颤心惊，但好在看到“<strong>制证中</strong>”我悬着的心终于落下了</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/9bce7ae38cd7a6b0e29c6b43fea996fc.jpg" alt="IMG_5072.jpg" class="img_OpE3"></p>
<p>也确实正如我对象所说，深圳办证无比高效，8天后收到由广州发来的 EMS 邮件，看到证件的那一刻我无比开心，终于可以去香港了😭</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/654b5960739af520c290bd51d5728fb4.png" alt="image.png" class="img_OpE3"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="赴港">赴港<a href="https://kuizuo.me/blog/first-trip-to-Hong-Kong#%E8%B5%B4%E6%B8%AF" class="hash-link" aria-label="赴港的直接链接" title="赴港的直接链接">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="过海关">过海关<a href="https://kuizuo.me/blog/first-trip-to-Hong-Kong#%E8%BF%87%E6%B5%B7%E5%85%B3" class="hash-link" aria-label="过海关的直接链接" title="过海关的直接链接">​</a></h3>
<p>在去往香港前，需要在营业厅软件开通漫游功能用于接收短信的，并购买境外流量套餐，同时手机 SIM 卡设置开启数据漫游，这样当你抵达香港时，信号显示 CMHK 时便可连上网络。</p>
<p>一早乘坐深圳北(起始站)抵达香港西九龙站(终点站)，全程不到 20 分钟，跟着人流抵达口岸，掏出港澳通行证来到<strong>自助通关闸机</strong>准备出境，当我放上我的通行证，机器提示我需要走人工通道，此时我看着对象已经穿过闸机，我又望向早已排满队列的人工通道，我心不由得凉了一截。跟对象说明了完情况后，便跑向人工通道开始排队，大约等待了 20 分钟，将证件递交给边检人员，她看了眼电脑屏幕，转头便询问起我来。</p>
<hr>
<p>边检人员：此行的目的？</p>
<p>我：陪对象去香港玩。</p>
<p>边检人员：待多久？</p>
<p>我：当晚回。</p>
<p>边检人员：返程的车票买了吗？</p>
<p>我：还没，不确认待到什么时候。</p>
<p>边检人员：做什么工作的？有没有工作群？</p>
<p>我：有的，<del>兄弟</del>，有的。(我将手机递给他看，并把与对象的聊天记录给她看）</p>
<p>边检人员：（稍微翻了翻我的手机，也没再过问，便放我过去）</p>
<blockquote>
<p>我斗胆猜测我的证件是因为我的户口是高危地区导致无法通过自助闸机。</p>
</blockquote>
<hr>
<p>成功出境后，我顺着人流穿过长长的过境通道，很快抵达香港入境边检处。在这里，我再次看到熟悉的<strong>自助通关闸机</strong>。这次总算是体验了完整了操作流程：先刷一下港澳通行证通过第一道闸门，然后在中间区域按压指纹，随后便可通过第二道闸门拿到过关小票便可正式入境香港。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/142113ba69c86e7f123d67847ad83ec5.jpg" alt="" class="img_OpE3"></p>
<p>出入境过程总结：内地出境边检 ➝ 内地海关 ➝ 香港入境边检 ➝ 香港海关 ➝ 入境香港</p>
<p>邪修出入境：问妈祖</p>
<div class="theme-admonition theme-admonition-info admonition_fh9h alert alert--info"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>tips</div><div class="admonitionContent_oz3Y"><p>如果可以，请不要带双肩包和行李箱（单肩包可以），这能给你省去海关安检时间（因为会查你所携带的物品）</p></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="开港卡">开港卡<a href="https://kuizuo.me/blog/first-trip-to-Hong-Kong#%E5%BC%80%E6%B8%AF%E5%8D%A1" class="hash-link" aria-label="开港卡的直接链接" title="开港卡的直接链接">​</a></h3>
<p>此行对我而言的目的很明确，就是<strong>线上开户港卡</strong>（汇丰，中银，众安）。
到了香港后，已经是午饭点了，在商场选了一家餐厅坐下，与对象的朋友会面，吃了顿铁板牛肉饭(72 港币=66人民币)。如果在深圳吃的同类别的食物，其实也没贵很多。</p>
<table><thead><tr><th><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/732e79675d74d17d688d26950a5ff78a.jpg" alt="IMG_5232.JPG" class="img_OpE3"></th><th><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/68864683ac470b98931578832d6dea72.png" alt="image.png" class="img_OpE3"></th></tr></thead></table>
<p>连上商场Wi-Fi，打开事先下载好的银行App，开始填写资料、上传出入境记录和通行证照片。全程不到 1 小时内，便完成了 3 张卡的开户，除了汇丰没有直接开通账户还需等待审核，中银与众安都是直接可以登录 app 了，之后在 app 上操作等待实体卡发件到大陆即可。</p>
<p>有了港卡后，至少资金跨境消费与支付可以省下一大笔的手续费，至于出金与投资这些都是基本操作了。不过手头还没有护照，还是有很多我想搞的账户是无法开通的，不过我想距离有护照以及第一次出国应该也不远了。</p>
<p>关于如何开港卡，外面的攻略多的数不清，这里就不在过多叙述细节，最终还是得根据自己的实际情况来操作。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="逛逛街">逛逛街<a href="https://kuizuo.me/blog/first-trip-to-Hong-Kong#%E9%80%9B%E9%80%9B%E8%A1%97" class="hash-link" aria-label="逛逛街的直接链接" title="逛逛街的直接链接">​</a></h3>
<p>因为有人带，我只需要一路跟进即可，饭后我便化身为工具人，一个无情的背包提货机器，陪对象及她的朋友去了趟铜锣湾的商圈逛了逛，扛着一大袋物品走了一下午。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/accf23671158dea64979eec3fea9a2cf.jpeg" alt="IMG_D6C08D1A89B3-1.jpeg" class="img_OpE3"></p>
<p>在逛街的过程中，我注意到香港街头的药店数量特别多，其中不乏一些真假难辨的店铺。店内大多售卖跌打油等药油，以及各类保健品，并且抢购的人还不少。尤其是下图这款药，火的离谱。很多大陆人都来香港买这个带回去。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/0d52f7bd9a9558154a1fba7dc3564ceb.png" alt="image.png" class="img_OpE3"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="离港">离港<a href="https://kuizuo.me/blog/first-trip-to-Hong-Kong#%E7%A6%BB%E6%B8%AF" class="hash-link" aria-label="离港的直接链接" title="离港的直接链接">​</a></h2>
<p>深圳去香港就是方便，当天去当天回，基本不用担忧车票余量。离港可就没赴港那么有”阻碍”，回去容易出来难。</p>
<p>也不知道下次去香港又会是何时，香港麦当劳还没吃，1000 元一晚的小小间的香港酒店还没住，不过在那之前，先把签注续上吧。（已经开始期待下一次香港行程了）</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="感受">感受<a href="https://kuizuo.me/blog/first-trip-to-Hong-Kong#%E6%84%9F%E5%8F%97" class="hash-link" aria-label="感受的直接链接" title="感受的直接链接">​</a></h2>
<p>由于此行比较匆忙，没有太多时间去感受香港这座城市，只能说是走马观花般的体验了一下，也算是领略了别一番的异域风情。</p>
<p>不过对我而言，这次去香港从心理上移除了我某须有的罪名。回想 23 年的夏天第一次去出入境办理港澳通行证被告知有”不良”记录不予出境，到 24 年夏天才得知该不良记录竟是”电诈“，于是尝试与工作人员解释事情的来龙去脉，总算给我提交到审查系统中。但又因我处在偷渡涉诈的高危地区(福建)，最终在核实阶段被公安人员告知我提供终止审查书才肯给我办理。</p>
<p><del>或许是我的不懈坚持打动了办证人员</del>，才有的此次香港之行。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>经历</category>
            <category>随笔</category>
            <category>旅行</category>
        </item>
        <item>
            <title><![CDATA[漫无目的的特种兵式旅行]]></title>
            <link>https://kuizuo.me/blog/special-forces-travel</link>
            <guid>https://kuizuo.me/blog/special-forces-travel</guid>
            <pubDate>Mon, 31 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[从小到大我都没有独自出过远门，更别说在异地他乡生活了。在农村老家待了整整四个月，心中渐生改变的渴望。于是，便有了此次为期二十天的江浙沪之旅。]]></description>
            <content:encoded><![CDATA[<p>从小到大我都没有独自出过远门，更别说在异地他乡生活了。在农村老家待了整整四个月，心中渐生改变的渴望。于是，便有了此次为期二十天的江浙沪之旅。</p>
<p>回顾整个旅程，说得好听些，倒像是一场特种兵式的旅行；说得不好听，则是漫无目的、四处奔波的流浪。我对旅行的想象过于简单，竟没能真正放下心来，细细体味每座城市独有的人文风情。</p>
<p>这段旅程也让我意识到，旅行的意义不只是到此一游的打卡，更是在陌生中与自己对话，在不确定里发现生活的丰富与可能。每一段匆忙的脚步背后，都隐藏着成长的印记和内心的蜕变。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="旅行地图">旅行地图<a href="https://kuizuo.me/blog/special-forces-travel#%E6%97%85%E8%A1%8C%E5%9C%B0%E5%9B%BE" class="hash-link" aria-label="旅行地图的直接链接" title="旅行地图的直接链接">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="第一站杭州36310">第一站：杭州(3.6~3.10)<a href="https://kuizuo.me/blog/special-forces-travel#%E7%AC%AC%E4%B8%80%E7%AB%99%E6%9D%AD%E5%B7%9E36310" class="hash-link" aria-label="第一站：杭州(3.6~3.10)的直接链接" title="第一站：杭州(3.6~3.10)的直接链接">​</a></h3>
<p>借着参加杭州 <a href="https://d2.alibabatech.com/" target="_blank" rel="noopener noreferrer">D2 终端技术大会</a>，让我有机会去杭州游玩的一番，来到西湖边上四处闲逛，发现了一个可以乘船游湖的项目，于是花了 50 块体验了悠然自得的水上时光。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/82cde27be09323c3e8e4ee1ec441e92e.jpeg" alt="1D4AA4DB-9342-4E1E-9679-6BCBAD093B0B_1_105_c.jpeg" class="img_OpE3"></p>
<p>或许是初到陌生城市的不适感作祟，让我早早结束了西湖之行，下船后随意找了家最近的霸王茶姬，独自漫步在回酒店的路上。耳机里放着熟悉的旋律，我却早已走神，不知自己在思索些什么。直到夜晚，一位朋友的消息打破了这份孤独的氛围，才让我从发散的思绪中抽离出来。</p>
<p>来杭州的第二天，便开启了面基推友、组局吃饭的流程。原本只是几个人的小聚，不知怎么人越拉越多，最后竟发展成了一场 15 人的大型饭局。连农村吃席都未必能凑出这么一桌人，那场面我就不放图了，自己脑补一下吧。</p>
<p>倒是从聊天中得知，许多大厂人基本都是晚上 9 点后才下班——听着都让人窒息。我很难想象，长此以往，那些被压缩的时间和生活感，会从一个人身上悄悄剥落掉多少东西。每天两点一线，久而久之，也就只剩下一条疲惫的轨迹。</p>
<p><strong>饭罢，归舍，盼明日。</strong></p>
<p>D2 大会举办地设在阿里巴巴的园区，场地真的气派非凡，最让我印象深刻的画面是“程序员的三种活法”。我忍不住自嘲：25 岁的我啊，只想活着（言外之意——躺平）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/54d69c852540efc66722af6689faa934.png" alt="image.png" class="img_OpE3"></p>
<p>第二天，我和一同来杭州的朋友一大早骑着单车去了西溪湿地。清晨的空气中弥漫着大自然的清香，让人神清气爽，整个人也轻松了不少。中午顺路去了趟山姆超市，在山姆餐厅吃了传说中的牛肉卷，味道还不错。也是这次才知道，原来山姆需要会员（260 元/年）才能进，好在朋友人脉在线，借到了一张只能消费一次的山姆体验卡。进去转了一圈，最后只买了一包咖啡豆就出来了。</p>
<p>都说杭州是个美食荒漠，但对于体重不到 100 斤、对美食无欲的我而言，却没有任何感知。如果考虑换一座城市生活，杭州似乎是个不错的选择。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="第二站绍兴311312">第二站：绍兴(3.11~3.12)<a href="https://kuizuo.me/blog/special-forces-travel#%E7%AC%AC%E4%BA%8C%E7%AB%99%E7%BB%8D%E5%85%B4311312" class="hash-link" aria-label="第二站：绍兴(3.11~3.12)的直接链接" title="第二站：绍兴(3.11~3.12)的直接链接">​</a></h3>
<p>带着随性闲逛江浙沪的心态，恰巧在网上认识了一位来自绍兴的老板，于是约好时间，从杭州东站搭乘不到半小时的动车前往绍兴。有人作陪的旅行与独自上路截然不同，那份原本的不安感也在交谈中悄然消散。</p>
<p>说来惭愧，起初听到“绍兴”这个名字，我脑海中竟一时想不出有什么与之相关的印象，直到在地图上看到“鲁迅故里”地铁站，才恍然意识到鲁迅原来是绍兴人。于是便去参观了鲁迅故居，亲身体验了《从百草园到三味书屋》的情境，那段从家后菜园“百草园”走到前面私塾“三味书屋”的童年故事仿佛鲜活地展现在眼前🤣。而这处占地颇广的老宅，竟真的是他家的故居，才意识到鲁迅真出身于当时的大家族——这便是老时的大户人家了。</p>
<p>想着来都来了，总该带点什么纪念，于是被 30 块的冰箱贴狠狠地收割了一波。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/6958ab0c154f8f5696e589849b067dee.jpeg" alt="A3DE6F43-BBC1-4CFD-A386-B7BC1F4CCDA2_1_105_c.jpeg" class="img_OpE3"></p>
<p>原以为绍兴除了鲁迅并无太多可说，直到从这位老板朋友口中得知，绍兴居民的人均可支配收入竟在浙江省名列第一，才让我大为惊讶。由此也深刻意识到，对一座城市的了解，不能只凭刻板印象，还得听听当地人的现身说法，才能窥见它更真实、更丰富的一面。</p>
<p>拖这位老板的福，吃了几顿大餐，顺带去做了个推拿和拔罐，师傅说我湿气很重，看样子要少喝冰饮，<del>多喝奶茶</del>。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/c0cc5f4c131cc1df175189d8038fbbd9.jpeg" alt="2AF2FE89-26E4-4C38-91B8-1A7FCA1A6E80.jpeg" class="img_OpE3"></p>
<p>在朋友的款待下，这趟绍兴之旅显得格外轻松惬意。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="第三站上海313314">第三站：上海(3.13~3.14)<a href="https://kuizuo.me/blog/special-forces-travel#%E7%AC%AC%E4%B8%89%E7%AB%99%E4%B8%8A%E6%B5%B7313314" class="hash-link" aria-label="第三站：上海(3.13~3.14)的直接链接" title="第三站：上海(3.13~3.14)的直接链接">​</a></h3>
<p>高楼林立的冲击是上海给我的震撼，外滩给了我一场视觉的盛宴。短短两天，根本无法看尽上海的全貌。但它给我留下的震撼感却久久不能散去。作为从农村出身的我，我第一次真切地感受到，什么叫做“城市的高度决定了你的眼界”。</p>
<table><thead><tr><th style="text-align:center">东方明珠</th><th style="text-align:center">陆家嘴三件套</th></tr></thead><tbody><tr><td style="text-align:center"><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/62d805d6b0bf25bcc4616e6f0f3e71dd.png" alt="" class="img_OpE3"></td><td style="text-align:center"><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/f292615a34eac8a0c4727e39183fecfc.jpeg" alt="" class="img_OpE3"></td></tr></tbody></table>
<p>走着走着发现有个观光隧道可以直达陆家嘴，于是花了 90 块钱买了张门票，我的评价是一坨🤡。</p>
<p>借着在字节工作朋友的访客，成功去大厂吃了份员工餐！第二天还去了趟美团园区吃了顿外卖（说笑的</p>
<table><thead><tr><th style="text-align:center">字节访客卡</th><th style="text-align:center">美团访客卡</th></tr></thead><tbody><tr><td style="text-align:center"><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/93e649410f9c38cfc5685c3961c2f031.png" alt="" class="img_OpE3"></td><td style="text-align:center"><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/d0984a9c5ef4839600717dc266a7557a.jpeg" alt="" class="img_OpE3"></td></tr></tbody></table>
<p>上海没敢多待，城市的节奏快得让人喘不过气。站在高楼林立之间，会让我感受到一种前所未有的渺小与冲击。只有真正置身其中时，才发现很多东西不是靠想象就能理解的。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="第四站苏州315">第四站：苏州(3.15)<a href="https://kuizuo.me/blog/special-forces-travel#%E7%AC%AC%E5%9B%9B%E7%AB%99%E8%8B%8F%E5%B7%9E315" class="hash-link" aria-label="第四站：苏州(3.15)的直接链接" title="第四站：苏州(3.15)的直接链接">​</a></h3>
<p>从上海虹桥到苏州园区只用不到 30 分时间，与网友约好在苏州中心商城的咖啡局，在湖西观望了金鸡湖的，畅谈了双方各自的故事，相比之下我的经历还是有些过于平庸了。</p>
<p>就当我准备聆听漫步回家时，发现我的右耳机掉了，好在有苹果的查找，让我成功找寻遗失的耳机，又一个爱上苹果的理由了。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/4cc213e898a508e0ad8b5516fe984268.png" alt="image.png" class="img_OpE3"></p>
<p>晚上与一位亲戚一同吃了下饭，顺便带我逛逛了苏州的街道，让我诧异的是街道的人员如此稀少，一开始我以为是阴雨天气导致的，于是我多嘴吐槽了一下，才得知原来苏州竟然没有夜生活，8 点之后大多数的店面都不营业了，他还和我说江南地区绝大多数的城市都这样（回想所去过的绍兴也是如此），生活节奏比较慢，家庭观念较重，小孩从小到大晚上都不出门，就导致这样的现象。或许这也是为什么苏州能被称之为适合宜居的城市之一。</p>
<p>受天气的影响，这几天在上海和苏州的体验并没有多好，回到酒店我便不由得开始想家(此时的我已经离家有 10 天了，一般旅游心理边际效应递减期是7天)，连续多天都独自一人住酒店孤独感在此时莫名的压抑😞，辗转反侧之余便定下了第二天下午回去的班次。</p>
<p>第二天一早去了苏州知名景点平江路，江南风情瞬间映入我的眼帘。小桥流水，仿佛置身于古画之中。街道两旁的古色古香的民居与店铺，与这片氤氲着历史气息的街景相得益彰。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/586fba805f0a1cf11e8ffcbcaa488875.jpeg" alt="58B2212E-0203-46F6-95EF-FCEC75C7A2A8_1_105_c.jpeg" class="img_OpE3"></p>
<p>从平江路一路向北，偶然看到茶颜悦色的门店，不由自主的开始排队(近半小时)，原来想要喝上它还需预点单，现场排队取茶，甚至不能外卖，只能现场饮用，不得不说味道是值得等待的。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/8a8260af248fe7089a8247fdc9271e6a.jpeg" alt="78A46B7D-5A7C-4598-8C5C-223422DE9FDC_4_5005_c.jpeg" class="img_OpE3"></p>
<p>走完平江路准备来到苏州博物馆时想要进入参观，被告知要提前预约才能够进入，一看预约单只能排到下周了（这就是不做攻略的代价🤡）</p>
<p>事已至此，不如提早抵达车站，乘坐回家的动车。到了车站之后，竟开始犹豫不决，过早的结束旅途似乎没给这段路程画个完美的句号。正当我在候车大厅踌躇时，一位苏州的网友发来消息：EDC China 电音节将于 3 月 22 日在苏州举办（此时是 3 月 16 日），而我又恰好是个电音迷。</p>
<p>抱着来都来了的心态。于是原本等待检票的我，在最后一刻取消了回程班次，改而临时搭上前往无锡的列车，给旅程续写一个意料之外的番外篇。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="第五站无锡316">第五站：无锡(3.16)<a href="https://kuizuo.me/blog/special-forces-travel#%E7%AC%AC%E4%BA%94%E7%AB%99%E6%97%A0%E9%94%A1316" class="hash-link" aria-label="第五站：无锡(3.16)的直接链接" title="第五站：无锡(3.16)的直接链接">​</a></h3>
<p>因为看过西湖，莫名的对湖有一种好感，从地图上看到了太湖的广大，于是约了位推友一同去了无锡贡湖湾彩虹步道，赏目太湖。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/aa76bbcf2f35c3b9f6e17c06f5d0f63b.png" alt="image.png" class="img_OpE3"></p>
<p>原本计划前往灵山胜境参拜灵山大佛，但受限于时间安排，只得作罢。恰好我有位发小在常州，于是无锡只停留了一天，便匆匆启程前往下一站。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="第六站常州317-318">第六站：常州(3.17-3.18)<a href="https://kuizuo.me/blog/special-forces-travel#%E7%AC%AC%E5%85%AD%E7%AB%99%E5%B8%B8%E5%B7%9E317-318" class="hash-link" aria-label="第六站：常州(3.17-3.18)的直接链接" title="第六站：常州(3.17-3.18)的直接链接">​</a></h3>
<p>一直住朋友家里边，基本没去玩什么，我对这座城市的评价请看下图</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/fec52b65c963fe101fb11af60c851e86.png" alt="image.png" class="img_OpE3"></p>
<p>此时的我才发现我其实不是在旅居，而是特种兵式旅行，毫无目的性随处逛逛的那种，只要有局我便随叫随到。以至于我几乎待了1-2天便换一个地方住，此时对于旅游本身关注点已经不在玩上了，本打算去南京的计划，但人生地不熟的， 也不知道去了能做什么，与其来回折腾自己，不如计划取消。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="第七站重返苏州321-323">第七站：重返苏州(3.21-3.23)<a href="https://kuizuo.me/blog/special-forces-travel#%E7%AC%AC%E4%B8%83%E7%AB%99%E9%87%8D%E8%BF%94%E8%8B%8F%E5%B7%9E321-323" class="hash-link" aria-label="第七站：重返苏州(3.21-3.23)的直接链接" title="第七站：重返苏州(3.21-3.23)的直接链接">​</a></h3>
<p>这次有了上次来苏州的经历，于是我提早预约了苏博的门票，带着没体验过博物馆的心态稍加浏览，不过我对博物馆内的展品并无兴趣，这里就一笔带过。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/2170de091fdba327e1818c5faf6746a8.jpeg" alt="381106FB-FADA-4B01-AD6B-B60632D62B8D_1_105_c.jpeg" class="img_OpE3"></p>
<p>下午便和一同去 EDC 的朋友会面，晚上在金鸡湖(湖东)观望着对岸，以不同的视角看，好天气对旅行的体验感提升是巨大的。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/6dea59314d2c8d227629f5b7b511527b.png" alt="2C64B089-D5A2-46DA-AFDF-D39F7A1D9D75_1_105_c.jpeg" class="img_OpE3"></p>
<p>不过这次的主题是听。当晚我们怀着激动的心情花了 40 分钟连骑带走来到 EDC 的会场，可惜非工作人员无法进入，但光是远远听着试音的部分，就已经让我们这些电音迷血脉偾张、热血沸腾。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/7d466e397154a49aa682da8625568dcc.jpeg" alt="2682A0CE-4017-4C92-8CC1-32C80C769103_1_102_o" class="img_OpE3"></p>
<p>第二天提早 1 小时来到会场，远远就能听到音浪从场馆深处一层层传出来。入场后，那股扑面而来的低音震得胸口发麻，耳朵在轰鸣中也逐渐适应了电音的节奏。只见得他们摇起来，我的身体便不由得跟着节奏开始动了起来。或者这就是电音的魔力吧——无需言语，无需默契，只要一拍即合的节奏，就能让所有人瞬间连接在一起。</p>
<p>此刻，现实的琐碎和压力全被抛到脑后，只有音乐、节奏，还有那个不愿停下的自己。当晚回程的路上，我们虽然疲惫，却像刚完成一场灵魂的洗礼。那一晚，我们不是观众，是参与者，是舞台的一部分，是电音世界的信徒。</p>
<video controls="" style="max-width:100%;height:auto"><source src="https://img.kuizuo.me/2025/f5e77f85bb069e1bf897a0b2aaa58a3f.mp4" type="video/mp4"></video>
<p>最后与这位朋友离别的时候，还顺走了一只秋田犬。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/c83eddc1e4f121ad42fabbd5966549dd.jpeg" alt="D0654C0D-D196-4AA4-9BE0-682EA2490E59_1_105_c.jpeg" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="最终站家324">最终站：家(3.24)<a href="https://kuizuo.me/blog/special-forces-travel#%E6%9C%80%E7%BB%88%E7%AB%99%E5%AE%B6324" class="hash-link" aria-label="最终站：家(3.24)的直接链接" title="最终站：家(3.24)的直接链接">​</a></h3>
<p>这一段不想多写，每当想起都会一时语塞，不禁落泪😭</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="经验分享">经验分享<a href="https://kuizuo.me/blog/special-forces-travel#%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB" class="hash-link" aria-label="经验分享的直接链接" title="经验分享的直接链接">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="攻略">攻略<a href="https://kuizuo.me/blog/special-forces-travel#%E6%94%BB%E7%95%A5" class="hash-link" aria-label="攻略的直接链接" title="攻略的直接链接">​</a></h3>
<p>我这个人更倾向于随机应变而非通盘计划，不擅长提前制定行程，更喜欢享受当下的过程，并在事后回顾总结。如果这时有人带我玩、我只需掏钱，那我大概率会屁颠屁颠地跟着走。</p>
<p>因此我很不喜欢做攻略，地图就是我的小攻略。“说走就走的旅行”，是我对“自由”最直接的兑现。</p>
<p>但现实是，漫无目的的游玩会大幅降低旅行体验，尤其是你抵达目的地却发现需要提前预约的时候。代码调试都要踩坑，更何况生活。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="酒店">酒店<a href="https://kuizuo.me/blog/special-forces-travel#%E9%85%92%E5%BA%97" class="hash-link" aria-label="酒店的直接链接" title="酒店的直接链接">​</a></h3>
<p>我几乎没什么订房经验(这是不是暴露了什么)，在此之前都是在美团订的单，从不看装修时间，只看距离，而这就导致十分容易住上一些体验非常糟糕的房间，结果往往踩坑——比如设施老旧、条件差等问题，缺乏统一标准。</p>
<p>后来我接触到了华住旗下的汉庭酒店，体验远胜一般民宿或小旅馆。</p>
<p>说个真实的烦心事：我在杭州西湖的第一晚，住的是美团上订的 283 元酒店，入住时被收了 300 元押金，还让我签反诈承诺书。我还花钱升级了所谓“高级大床房”，结果房间跟普通房毫无差别，体验极差。</p>
<p>而入住华住系酒店就方便得多——身份证一递，稍等片刻就能拿到房卡。房间的干净程度、布置标准，也远胜一筹。没有对比就没有伤害。当然对钱包的伤害也挺大的。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="食物">食物<a href="https://kuizuo.me/blog/special-forces-travel#%E9%A3%9F%E7%89%A9" class="hash-link" aria-label="食物的直接链接" title="食物的直接链接">​</a></h3>
<p>出门在外，有外卖吃倒不是很成问题，倒是喝个热水或是泡个茶包/咖啡包尤为困难，因为你不确信酒店的热水壶是否泡过什么不明物体，是否卫生。</p>
<p>如果可以带个<strong>便携式热水杯</strong>(小米)，比普通水杯略大，解决“热水自由”不在话下。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="衣着">衣着<a href="https://kuizuo.me/blog/special-forces-travel#%E8%A1%A3%E7%9D%80" class="hash-link" aria-label="衣着的直接链接" title="衣着的直接链接">​</a></h3>
<p>既然出远门，穿着上肯定不能像在家那样随意。<strong>一次性用品</strong>（内裤、袜子）非常实用，不必担心换洗问题。其实带上 2~3 套衣物就足够了，前提是你住的酒店配备有<strong>洗衣机和烘干机</strong>——这又回到了上文说的住宿体验问题。</p>
<p>如果可以记得带 1-2 套一次性的<strong>沐浴用品</strong>(如毛巾、浴巾、拖鞋)，以免会有住在朋友家里的情况。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="出行">出行<a href="https://kuizuo.me/blog/special-forces-travel#%E5%87%BA%E8%A1%8C" class="hash-link" aria-label="出行的直接链接" title="出行的直接链接">​</a></h3>
<p>不得不感叹国内交通的便利——高铁的效率远超飞机**，几乎成了我首选。不需要提前两小时抵达机场、不用脱鞋安检，也不怕天气延误。</p>
<p>如果你常坐一等座，会发现很难抢到 1F 的座位，那通常是乘警的工作席（我曾坐过 1D 的位置）。若你不小心选到了这个位置，又略微社恐，可以考虑改签到其他位置。</p>
<p>高铁网络波动较大，我会趁这个空档听听歌、翻翻相册、记录些文字，也算一种旅途中的“放空”。记得带个<strong>眼罩或墨镜</strong>，睡得更香。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="记录">记录<a href="https://kuizuo.me/blog/special-forces-travel#%E8%AE%B0%E5%BD%95" class="hash-link" aria-label="记录的直接链接" title="记录的直接链接">​</a></h3>
<p>我不擅长拍照，也没有什么技巧，但我很喜欢苹果相册的一个功能：<strong>它会根据拍摄地点自动在地图上打点，并按位置整理照片</strong>。无形中帮我记录下那些风景与当下。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/13b19037aee12081b145ec92605a3981.png" alt="image.png" class="img_OpE3"></p>
<p>不过记得请关闭图片的网络权限，否则 Icloud 会在后台悄悄同步照片，一不留神流量就没了（我就是这样被莫名其妙地扣了近 50 元）。</p>
<p>还有就是在录制视频时如果是 <strong>4K 60fps</strong>，那手机存储很快就会告急。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/2c1023c761b177d3ced072a7e9ada9f3.png" alt="image.png" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="开销">开销<a href="https://kuizuo.me/blog/special-forces-travel#%E5%BC%80%E9%94%80" class="hash-link" aria-label="开销的直接链接" title="开销的直接链接">​</a></h3>
<p>我并未对本次的旅行做一个明细的开支记录，不过可从消费账单记录略知大概。</p>
<table><thead><tr><th style="text-align:center">微信</th><th style="text-align:center">支付宝</th></tr></thead><tbody><tr><td style="text-align:center"><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/8eaf63394faecab05764cc8c9091a9d9.jpg" alt="IMG_3614" class="img_OpE3"></td><td style="text-align:center"><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2025/ad0333202163a2ebb906a3604b149a8b.jpg" alt="" class="img_OpE3"></td></tr></tbody></table>
<p>因为辗转了许多城市，所以出行上的消费也占了很大一笔。但出去玩最主要的还是在居住上。不过其中最贵的一笔是 EDC 电音节的门票。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="感悟">感悟<a href="https://kuizuo.me/blog/special-forces-travel#%E6%84%9F%E6%82%9F" class="hash-link" aria-label="感悟的直接链接" title="感悟的直接链接">​</a></h2>
<p>如果用一个成语来形容这次旅行，那便是“<strong>漫无目的”</strong>。</p>
<p>究其原因，还是源于我的性格。我是一个很宅的人，哪怕只是独自去趟小卖部，都会感到不自在，更别提一个人独自在外旅行了。这种不安感像是与生俱来的，总觉得和周围人之间隔着一道无形的墙。时间久了，孤独感便悄然滋生。</p>
<p>我不敢去想象一个人身处陌生城市、身边没有朋友的感觉。或许，如果身边能有一个人陪着一起旅行，一切都会变得不一样。嗯，看来是时候考虑找个对象了。</p>
<p>再加上我有严重的选择困难症，连吃顿饭都能犹豫半天。有段时间甚至靠“随机餐食选择器”才能决定下一顿吃什么。独自出门旅行本就挺无聊的，尤其在没有人带路、自己又不擅长做攻略的情况下，更加显得手足无措。</p>
<p>旅行，并不总是轻松惬意的度假。它更多时候意味着一种尝试，一种挑战。这次旅程虽然留有些许遗憾，还有许多未能尝试的美食、未曾到达的地方。但正因如此，相较于吃喝玩乐本身，体验才显得值得留念。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>经历</category>
            <category>旅游</category>
            <category>生活</category>
        </item>
        <item>
            <title><![CDATA[2024 · 应届牛马到远程自由]]></title>
            <link>https://kuizuo.me/blog/2024-year-end-summary</link>
            <guid>https://kuizuo.me/blog/2024-year-end-summary</guid>
            <pubDate>Tue, 31 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[面临着最后半年的大学生涯，与大多数大学生不同，我无时不在期盼着毕业这天的到来。步入职场进入了一家 AI 初创公司开始了"牛马"生活。最终我选择在国庆前裸辞，回到老家开始黑猴 play，却未曾料到，一份远程工作机会和一位新伙伴🐱悄然而至…]]></description>
            <content:encoded><![CDATA[<p>面临着最后半年的大学生涯，与大多数大学生不同，我无时不在期盼着毕业这天的到来。步入职场进入了一家 AI 初创公司开始了"牛马"生活。最终我选择在国庆前裸辞，回到老家开始黑猴 play，却未曾料到，一份远程工作机会和一位新伙伴🐱悄然而至…</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="迟到的毕业证书">迟到的毕业证书<a href="https://kuizuo.me/blog/2024-year-end-summary#%E8%BF%9F%E5%88%B0%E7%9A%84%E6%AF%95%E4%B8%9A%E8%AF%81%E4%B9%A6" class="hash-link" aria-label="迟到的毕业证书的直接链接" title="迟到的毕业证书的直接链接">​</a></h2>
<p>由于之前经历了转专业+休学以及一系列旷课行为导致挂科学分不够，最终不得不在临近毕业时为这些“买单”（出来混总是要还的）。在大四下的时候，有几场考试的时间被安排在发毕业证之前，这意味着我会比他人晚拿到毕业证(大约 2 周)。</p>
<p>不过因祸得福的是，由于延毕所导致我的学生认证资格一直延续到现在，预估明年 6 月毕业季时，我的学生认证才会真正到期。</p>
<img src="https://img.kuizuo.me/2024%2F1231191115-20241231191114.png" width="800">
<p>要我说读大学对我有什么用的话，我能回答的或许只有学生认证所带来的各种优惠，而非传统大学教育为我提供了什么有价值的东西。</p>
<p>上大学对我说算是一种束缚，甚至让我一度怀疑我所读的不是一所的“大学”，反而是所“监狱”。</p>
<p>上大学也是我愧对于父母的一件事情，我也一直不愿意与他们诉说我在大学期间做了什么，因为解释给他们晦涩难懂，何况大学期间发生的变故差点让我难以毕业。</p>
<p>每当他人提起我的学校，我总会有点莫名的哽咽，因为大学很垃圾，难以说出口，我一直不愿说我是本校的学生，且有意为之将有关学校的信息隐秘于世，宁可告诉别人我没读过大学，也不愿让其知道大学名字。</p>
<p>或许当年直接选择辍学而非休学能真正抹除自己内心的阴霾。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="初次面基">初次面基<a href="https://kuizuo.me/blog/2024-year-end-summary#%E5%88%9D%E6%AC%A1%E9%9D%A2%E5%9F%BA" class="hash-link" aria-label="初次面基的直接链接" title="初次面基的直接链接">​</a></h2>
<p>在结束了毕业论文答辩，于是决定去广州与网友面基。这是我第一次网友线下面基，再次之前我甚至都没有去过北上广深，甚至这是我自己第一次一个人出省玩🥲。</p>
<p>面基的第一天网友就带我体验了 livehouse，还用无人机拍下广州塔的美景。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024%2F1231181558-IMG_1456.jpg" alt="IMG_1456.JPG" class="img_OpE3"></p>
<p>后面一起去深圳参加了 VueConf 大会，也是我第一次参加这种大型技术活动。之后还计划一同去🇭🇰搞卡，然而和去年一样，由于特殊原因，今年的我依旧办理不了港澳通行证😔</p>
<p>下半年参加 Feday 有幸见到了一些大佬，技术的氛围感，让我感受到了一种前所未有的激动。明年的我一定要面基更多的网友，去更多的地方，体验更多的生活。</p>
<p>结束了短暂的游玩时光，终于要开始苦逼的牛马生活了，吗？</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="初入职场">初入职场<a href="https://kuizuo.me/blog/2024-year-end-summary#%E5%88%9D%E5%85%A5%E8%81%8C%E5%9C%BA" class="hash-link" aria-label="初入职场的直接链接" title="初入职场的直接链接">​</a></h2>
<p>和绝大多数刚毕业的应届生一样，在家人的建议和考虑下，我最终选择去福州和我姐一起生活和工作，这样既能照应彼此，减轻一些生活压力的同时，还能提高生活幸福指数。</p>
<p>事实也确实如此，在我姐的几年生活经验下，我几乎不用考虑衣行吃住，在福州过得是有滋有味。一向宅男的我在我姐的怂恿下尝试了很多不一样生活的方式，并不是只有宅在家里才是活着（虽然在家里活着确实挺舒服的😂）。</p>
<p>在五一的节假日期间，我准备完简历与项目后开始找工作去，这一段的求职与就业的感悟故事我也记录了下来。</p>
<p><a href="https://kuizuo.me/blog/experience-of-an-ai-company" target="_blank" rel="noopener noreferrer">记 · 在 AI 公司入职一个月的体验与感悟</a></p>
<p>然而在工作了三个月后，我逐渐发现公司内部异常混乱。在这段时间里，我被卷入高管之间的内斗，沦为他们博弈的工具人。试用期结束时，不但被卡转正，还遭遇了疯狂的职场PUA和无止境的压榨。</p>
<p>就在我萌生离职念头时，没想到我的前领导竟然比我更快一步离职。而我却做出了一个让我至今懊悔的决定——接手前领导的活，还选择吃下老板画的大饼。在这种超负荷的状态最终只让我坚持了一个月。9月30日这一天，我终于下定决心递交了辞呈，给自己放了一个真正的长假。😇</p>
<p>期间所发生的故事尤为精彩，我也将其整理了下来，也算是给我的职业生涯增添了浓墨重彩的一笔。</p>
<div>Loading...</div>
<p>至于说现在这个 AI 初创公司发展的如何，或许是在濒临倒闭的边缘，不过我现在只清晰地记得离职那天一刻无比愉悦的心情😆。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="游戏艺术">游戏艺术<a href="https://kuizuo.me/blog/2024-year-end-summary#%E6%B8%B8%E6%88%8F%E8%89%BA%E6%9C%AF" class="hash-link" aria-label="游戏艺术的直接链接" title="游戏艺术的直接链接">​</a></h2>
<p>离职回到老家的我第一时间玩起了《黑神话·悟空》，之前玩的是《黑公司·牛马》。我特意为此购入了XBox手柄，只为离职之后的娱乐尽兴一把。</p>
<p>今年年初在朋友的怂恿下玩《荒野大镖客 2》。也是从这部作品刷新了我对游戏的认知，让我开始对这类游戏产生了浓厚兴趣，让我意识到游戏不仅仅是一个娱乐方式，更像是部电影、艺术，每一帧画面、每一段配乐、每一个剧情都蕴含着制作者的匠心。不仅是对自己兴趣的一次探索，也是一种对高品质游戏制作的致敬。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="远程工作">远程工作<a href="https://kuizuo.me/blog/2024-year-end-summary#%E8%BF%9C%E7%A8%8B%E5%B7%A5%E4%BD%9C" class="hash-link" aria-label="远程工作的直接链接" title="远程工作的直接链接">​</a></h2>
<p>就当我即将结束我的长假之时，一则私信引起了我的注意。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024%2F1231181558-imageundefined1.png" alt="image.png" class="img_OpE3"></p>
<p>对方看到我的技术栈与他的项目十分贴合，阅读了我职场故事会（原来写小作文真有用啊），便向我抛出橄榄枝，但当时的我还没那么想要再次进入工作状态，况且这家公司又是 AI 初创公司（我对初创公司已经有 PTSD 了，生怕再经历类似的职场故事🥲），所以当时的我对这份机会此挺无所谓的。</p>
<p>后面进一步的了解，发现是老板自己搞 B2B 的项目，不愿招外包，于是就在 X 上招聘，并找到了我。于是安排了场 2 小时视频通话，聊了聊我过往的项目经历，就这样拿到了该 Remote 的 Offer 😄，同时也多了一位美国朋友。</p>
<p>即便没有这个 Remote 机会，我也会选择尝试寻求其他海外的远程工作机会，因为我已经对坐班工作产生了厌倦感，我想要的更多是自由，而非束缚。</p>
<p>虽说是初创公司，但各种合同手续也都齐全，还是先发工资再干活（用老板的话讲就是：他先发薪资是我欠老板的，我先干活再拿钱是我欠老板的），时间上自由不少(就是要倒时差)， AI 工具服务必须齐全。本来还答应圣诞给我换台 Macbook，不过我还没到换设备的程度（其实是我觉得换设备迁移数据太麻烦了）。</p>
<p>业余时间的视频会议还让我看了真🔫，大🚬，这很🇺🇸特色。不过对我最不适应的还是来自于沟通而非技术。也让我深刻意识到了英语在远程工作，乃至未来职业发展的重要性。</p>
<p>去年这时候我对我未来的职业规划有所思考，其中就提到了<a href="https://kuizuo.me/blog/2023-year-end-summary#%E8%BF%9C%E7%A8%8B%E5%B7%A5%E4%BD%9C" target="_blank" rel="noopener noreferrer">远程工作</a>。如今我确实达到了当初对职场的期望。</p>
<p>或许是因为过往的职场经历在简历上不够看，大厂经历或许能给职业生态添金，加上学历的自卑，总让我对大厂一直怀有向往。但当我与一些大厂员工的谈话之后，他们反倒羡慕我的远程工作生活，至少不用 996，不用坐班。或许这就是人们总是向往他人的生活，却不知对方的苦恼。</p>
<h2>🐈</h2>
<p>突如其来的<a href="https://x.com/kuizuo/status/1858014221541453986" target="_blank" rel="noopener noreferrer">变故</a>致使我不得不马上搬家，正当决定新住所的时候，脑海里浮现直接回到老家生活并开始 Remote 工作的决定，并且还能迎接了我的新伙伴😽</p>
<img src="https://img.kuizuo.me/2024%2F1231181558-imageundefined2.png" width="600" alt="cat.png">
<p>我一毕业就想养猫，但我姐的鼻子对味道特别敏感，与她同住就更不可能了（这才是我回老家的最大原因），因为搬家的变故，养猫成为了现实。</p>
<p>养猫让我身心舒畅，但也让我分心乏力。关于养猫的记录，我想过段时间经历了更多再做更细致的分享，是一篇劝退指南😿。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="给自己的话">给自己的话<a href="https://kuizuo.me/blog/2024-year-end-summary#%E7%BB%99%E8%87%AA%E5%B7%B1%E7%9A%84%E8%AF%9D" class="hash-link" aria-label="给自己的话的直接链接" title="给自己的话的直接链接">​</a></h2>
<p>相比以往在大学期间自我摸索技术，今年尝试了很多之前不曾接触的事情。网友面基，参加技术大会，游玩方特，远程工作，养猫等等，相比之前大学期间只会宅在家里专研技术，却不懂的体验生活的我，或许是因为喜欢折腾的性格，也或许是因为大学期间的压抑，让我在现在更加渴望体验生活。</p>
<p>这一年，从独自摸索到与团队协作，从封闭的校园到远程工作，也让我逐渐认识到，技术只是工具，技术的提升可能是人生中最不起眼的东西，更有意义的是这个过程的体验、成长、感悟。</p>
<p>感谢自己愿意尝试未知，希望这份勇于尝试的心态让我始终保持热爱与激情。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="往年回顾">往年回顾<a href="https://kuizuo.me/blog/2024-year-end-summary#%E5%BE%80%E5%B9%B4%E5%9B%9E%E9%A1%BE" class="hash-link" aria-label="往年回顾的直接链接" title="往年回顾的直接链接">​</a></h2>
<ul>
<li><a href="https://kuizuo.me/blog/2023-year-end-summary">2023 · 谈谈职业规划</a></li>
<li><a href="https://kuizuo.me/blog/narrate-a-college-student">2023 · 叙一名转专业+休学的大学生经历</a></li>
<li><a href="https://kuizuo.me/blog/2022-year-end-summary">2022 · 逆向到Web开发</a></li>
<li><a href="https://kuizuo.me/blog/2021-year-end-summary">2021 · 休学一年</a></li>
<li><a href="https://kuizuo.me/blog/2020-year-end-summary">2020 · 编程之旅-起点</a></li>
</ul>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>年终总结</category>
            <category>工作</category>
        </item>
        <item>
            <title><![CDATA[Next.js 使用 Hono 接管 API]]></title>
            <link>https://kuizuo.me/blog/nextjs-with-hono</link>
            <guid>https://kuizuo.me/blog/nextjs-with-hono</guid>
            <pubDate>Wed, 02 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[这篇文章详细介绍了如何在 Next.js 项目中使用 Hono 框架来接管 API 路由，以解决 Next.js 自带 API Routes 功能的限制。并探讨了集成步骤、数据验证、错误处理、RPC功能等方面，并提供了实用的代码示例和优化建议。]]></description>
            <content:encoded><![CDATA[<p>直入正题，Next.js 自带的 API Routes (现已改名为 <a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers" target="_blank" rel="noopener noreferrer"><strong>Route Handlers</strong></a>) 异常难用，例如当你需要编写一个 RESTful API 时，尤为痛苦</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024%2F0930171329-image.png" alt="image.png" class="img_OpE3"></p>
<p>这还没完，当你需要数据验证、错误处理、中间件等等功能，又得花费不小的功夫，所以 Next.js 的 API Route 更多是为你的全栈项目编写一些简易的 API 供外部服务，这也可能是为什么 Next.js 宁可设计 <a href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations" target="_blank" rel="noopener noreferrer">Server Action</a> 也不愿为 API Route 提供传统后端的能力。</p>
<p>但不乏有人会想直接使用 Next.js 来编写这些复杂服务，恰好 <a href="https://hono.dev/docs/getting-started/vercel" target="_blank" rel="noopener noreferrer">Hono.js</a> 便提供相关能力。</p>
<p>这篇文章就带你在 Next.js 项目中要如何接入 Hono，以及开发可能遇到的一些坑点并如何优化。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="nextjs-中使用-hono">Next.js 中使用 Hono<a href="https://kuizuo.me/blog/nextjs-with-hono#nextjs-%E4%B8%AD%E4%BD%BF%E7%94%A8-hono" class="hash-link" aria-label="Next.js 中使用 Hono的直接链接" title="Next.js 中使用 Hono的直接链接">​</a></h2>
<p>可以按照 <a href="https://hono.dev/docs/getting-started/vercel#_1-setup" target="_blank" rel="noopener noreferrer">官方的 cli</a> 搭建或者照 next.js 模版 <a href="https://github.com/vercel/hono-nextjs" target="_blank" rel="noopener noreferrer">https://github.com/vercel/hono-nextjs</a> 搭建，核心代码 <code>app/api/[[...route]]/route.ts</code> 的写法如下所示。</p>
<div class="language-jsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-jsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword module" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Hono</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hono'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> handle </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hono/vercel'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> app </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Hono</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">basePath</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'/api'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'/hello'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token parameter">c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword control-flow" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">json</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token literal-property property" style="color:hsl(5, 74%, 59%)">message</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'Hello Next.js!'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token constant" style="color:hsl(35, 99%, 36%)">GET</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">handle</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token constant" style="color:hsl(35, 99%, 36%)">POST</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">handle</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token constant" style="color:hsl(35, 99%, 36%)">PUT</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">handle</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token constant" style="color:hsl(35, 99%, 36%)">DELETE</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">handle</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>从 <code>hono/vercel</code> 导入的 <code>handle</code> 函数会将 app 实例下的所有请求方法导出，例如 GET、POST、PUT、DELETE 等。</p>
<p>一开始的 User CRUD 例子，则可以将其<strong>归属到一个文件内</strong>下，这里我不建议将后端业务代码放在 app/api 下，因为 Next.js 会自动扫描 app 下的文件夹，这可能会导致不必要的热更新，并且也不易于服务相关代码的拆分。而是在根目录下创建名为 server 的目录，并将有关后端服务的工具库(如 db、redis、zod)放置该目录下以便调用。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024%2F0930171342-imageundefined1.png" alt="image.png" class="img_OpE3"></p>
<p>至此 next.js 的 api 接口都将由 hono.js 来接管，接下来只需要按照 Hono 的开发形态便可。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="数据效验">数据效验<a href="https://kuizuo.me/blog/nextjs-with-hono#%E6%95%B0%E6%8D%AE%E6%95%88%E9%AA%8C" class="hash-link" aria-label="数据效验的直接链接" title="数据效验的直接链接">​</a></h2>
<p>zod 可以说是 TS 生态下最优的数据验证器，hono 的 <code>@hono/zod-validator</code> 很好用，用起来也十分简单。</p>
<div class="language-jsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-jsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword module" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> z </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'zod'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> zValidator </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@hono/zod-validator'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Hono</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hono'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> paramSchema </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">object</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token literal-property property" style="color:hsl(5, 74%, 59%)">id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">cuid</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> jsonSchema </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">object</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token literal-property property" style="color:hsl(5, 74%, 59%)">status</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">boolean</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> app </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Hono</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">put</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token string" style="color:hsl(119, 34%, 47%)">'/users/:id'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">zValidator</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'param'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> paramSchema</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">zValidator</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'json'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> jsonSchema</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token parameter">c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> id </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">req</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">valid</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'param'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> status </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">req</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">valid</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'json'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// 逻辑代码...</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword control-flow" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">json</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">default</span><span class="token plain"> app</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>支持多种验证目标(param,query,json,header 等)，以及 TS 类型完备，这都不用多说。</p>
<p>但此时触发数据验证失败，响应的结果令人不是很满意。下图为访问 <code>/api/todo/xxx</code> 的响应结果（其中 xxx 不为 cuid 格式，因此抛出数据验证异常)</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024%2F0930171510-imageundefined2.png" alt="image.png" class="img_OpE3"></p>
<p>所返回的响应体是完整的 zodError 内容，并且状态码为 400</p>
<div class="theme-admonition theme-admonition-tip admonition_fh9h alert alert--success"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>提示</div><div class="admonitionContent_oz3Y"><p>数据验证失败的状态码通常为 <strong><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/422" target="_blank" rel="noopener noreferrer">422</a></strong></p></div></div>
<p>因为 zod-validator 默认以 json 格式返回整个 result，代码详见 <a href="https://github.com/honojs/middleware/blob/main/packages/zod-validator/src/index.ts#L68-L70" target="_blank" rel="noopener noreferrer">zod-validator/src/index.ts#L68-L70</a></p>
<p>这就是坑点之一，返回给客户端的错误信息肯定不会是以这种格式。这里我将其更改为全局错误捕获，做法如下</p>
<ol>
<li>复制 <a href="https://github.com/honojs/middleware/blob/main/packages/zod-validator/src/index.ts" target="_blank" rel="noopener noreferrer">zod-validator 文件</a>并粘贴至 <code>server/api/validator.ts</code>，并将 return 语句更改为 throw 语句。</li>
</ol>
<div class="language-diff codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-diff codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token unchanged prefix unchanged"> </span><span class="token unchanged line">  if (!result.success) {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token unchanged line"></span><span class="token deleted-sign deleted prefix deleted" style="color:hsl(5, 74%, 59%);text-decoration-line:line-through">-</span><span class="token deleted-sign deleted line" style="color:hsl(5, 74%, 59%);text-decoration-line:line-through">    return c.json(result, 400)</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token deleted-sign deleted line" style="color:hsl(5, 74%, 59%);text-decoration-line:line-through"></span><span class="token unchanged prefix unchanged"> </span><span class="token unchanged line">  }</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token unchanged line"></span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token unchanged prefix unchanged"> </span><span class="token unchanged line">  if (!result.success) {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token unchanged line"></span><span class="token inserted-sign inserted prefix inserted" style="color:hsl(119, 34%, 47%);text-decoration-line:underline">+</span><span class="token inserted-sign inserted line" style="color:hsl(119, 34%, 47%);text-decoration-line:underline">    throw result.error</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token inserted-sign inserted line" style="color:hsl(119, 34%, 47%);text-decoration-line:underline"></span><span class="token unchanged prefix unchanged"> </span><span class="token unchanged line">  }</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<ol start="2">
<li>在 <code>server/api/error.ts</code> 中，编写 handleError 函数用于统一处理异常。（后文前端请求也需要统一处理异常）</li>
</ol>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> z </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'zod'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">type</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> </span><span class="token maybe-class-name">Context</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hono'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">HTTPException</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hono/http-exception'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">handleError</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">err</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token known-class-name class-name" style="color:hsl(35, 99%, 36%)">Error</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> c</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token maybe-class-name">Context</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token maybe-class-name">Response</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">err </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">instanceof</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token known-class-name class-name" style="color:hsl(35, 99%, 36%)">ZodError</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> firstError </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> err</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">errors</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">json</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> code</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">422</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> message</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token template-string string" style="color:hsl(119, 34%, 47%)">\`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token template-string interpolation">firstError</span><span class="token template-string interpolation punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token template-string interpolation">path</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token template-string string" style="color:hsl(119, 34%, 47%)">\`: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token template-string interpolation">firstError</span><span class="token template-string interpolation punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token template-string interpolation">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token number" style="color:hsl(35, 99%, 36%)">422</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// handle other error, e.g. ApiError</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">json</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      code</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">500</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      message</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'出了点问题, 请稍后再试。'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> status</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">500</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<ol start="3">
<li>在 <code>server/api/index.ts</code> ，也就是 hono app 对象中绑定错误捕获。</li>
</ol>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> app </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Hono</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">basePath</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'/api'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">onError</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">handleError</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<ol start="4">
<li>更改 zValidator 导入路径。</li>
</ol>
<div class="language-diff codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-diff codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token deleted-sign deleted prefix deleted" style="color:hsl(5, 74%, 59%);text-decoration-line:line-through">-</span><span class="token deleted-sign deleted line" style="color:hsl(5, 74%, 59%);text-decoration-line:line-through"> import { zValidator } from '@hono/zod-validator'</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token deleted-sign deleted line" style="color:hsl(5, 74%, 59%);text-decoration-line:line-through"></span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token inserted-sign inserted prefix inserted" style="color:hsl(119, 34%, 47%);text-decoration-line:underline">+</span><span class="token inserted-sign inserted line" style="color:hsl(119, 34%, 47%);text-decoration-line:underline"> import { zValidator } from '@/server/api/validator'</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>这样就将错误统一处理，响应体也自定义，且后续自定义业务错误也同样如此。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024%2F1003095801-20241003095800.png" alt="" class="img_OpE3"></p>
<div class="theme-admonition theme-admonition-note admonition_fh9h alert alert--secondary"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>顺带一提</div><div class="admonitionContent_oz3Y"><p>如果需要让 zod 支持中文错误提示，可以使用 <a href="https://www.npmjs.com/package/zod-i18n-map" target="_blank" rel="noopener noreferrer">zod-i18n-map</a></p></div></div>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="rpc">RPC<a href="https://kuizuo.me/blog/nextjs-with-hono#rpc" class="hash-link" aria-label="RPC的直接链接" title="RPC的直接链接">​</a></h2>
<p>Hono 有个特性我很喜欢也很好用，可以像 <a href="https://trpc.io/" target="_blank" rel="noopener noreferrer">TRPC</a> 那样，导出一个 <a href="https://hono.dev/docs/guides/rpc#client" target="_blank" rel="noopener noreferrer">client</a> 供前端直接调用，省去编写前端 api 调用代码以及对应的类型。</p>
<p>这里我不想在过多叙述 RPC(可见我之前所写有关 <a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#end-to-end-typesafe-apis%E7%AB%AF%E5%88%B0%E7%AB%AF%E7%B1%BB%E5%9E%8B%E5%AE%89%E5%85%A8" target="_blank" rel="noopener noreferrer">TRPC 的使用</a>)，直接来说说有哪些注意点。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="链式调用">链式调用<a href="https://kuizuo.me/blog/nextjs-with-hono#%E9%93%BE%E5%BC%8F%E8%B0%83%E7%94%A8" class="hash-link" aria-label="链式调用的直接链接" title="链式调用的直接链接">​</a></h3>
<p>还是以 User CRUD 的代码为例，不难发现 <code>.get</code> <code>.post</code> <code>.put</code> 都是以链式调用的写法来写的，一旦拆分后，此时接口还是能够调用，但这将会丢失此时路由对应的类型，导致 client 无法使用获取正常类型，使用链式调用的 app 实例化对象则正常。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024%2F0930171730-imageundefined3.png" alt="image.png" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="替换原生-fetch-库">替换原生 Fetch 库<a href="https://kuizuo.me/blog/nextjs-with-hono#%E6%9B%BF%E6%8D%A2%E5%8E%9F%E7%94%9F-fetch-%E5%BA%93" class="hash-link" aria-label="替换原生 Fetch 库的直接链接" title="替换原生 Fetch 库的直接链接">​</a></h3>
<p>hono 自带的 fetch 或者说原生的 fetch 非常难用，为了针对业务错误统一处理，因此需要选用请求库来替换，这里我的选择是 <a href="https://www.npmjs.com/package/ky" target="_blank" rel="noopener noreferrer">ky</a>，因为他的写法相对原生 fetch 更友好一些，并且不会破坏 hono 原有类型推导。</p>
<p>在 <code>lib/api-client.ts</code> 编写以下代码</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">AppType</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@/server/api'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> hc </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hono/client'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports">ky</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'ky'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> baseUrl </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  process</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">NODE_ENV</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">===</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'development'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token operator" style="color:hsl(221, 87%, 60%)">?</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'http://localhost:3000'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">NEXT_PUBLIC_APP_URL</span><span class="token operator" style="color:hsl(221, 87%, 60%)">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> fetch </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> ky</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">extend</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  hooks</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    afterResponse</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">_</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> __</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> response</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token maybe-class-name">Response</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">response</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">ok</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> response</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">else</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">throw</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> response</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">json</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> client </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:hsl(221, 87%, 60%)">hc</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token generic-function generic class-name" style="color:hsl(35, 99%, 36%)">AppType</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">baseUrl</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  fetch</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> fetch</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>这里我是根据请求状态码来判断本次请求是否为异常，因此使用 response.ok，而响应体正好有 message 字段可直接用作 Error message 提示，这样就完成了前端请求异常处理。</p>
<p>至于说请求前自动添加协议头、请求后的数据转换，这就属于老生常谈的东西了，这里就不多赘述，根据实际需求编写即可。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="请求体与响应体的类型推导">请求体与响应体的类型推导<a href="https://kuizuo.me/blog/nextjs-with-hono#%E8%AF%B7%E6%B1%82%E4%BD%93%E4%B8%8E%E5%93%8D%E5%BA%94%E4%BD%93%E7%9A%84%E7%B1%BB%E5%9E%8B%E6%8E%A8%E5%AF%BC" class="hash-link" aria-label="请求体与响应体的类型推导的直接链接" title="请求体与响应体的类型推导的直接链接">​</a></h3>
<p>配合 react-query 可以更好的获取类型安全。此写法与 tRPC 十分相似，相应代码 → <a href="https://trpc.io/docs/client/react/infer-types" target="_blank" rel="noopener noreferrer">Inferring Types</a></p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token comment" style="color:hsl(230, 4%, 64%)">// hooks/users/use-user-create.ts</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> client </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@/lib/api-client'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">InferRequestType</span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">InferResponseType</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hono/client'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> useMutation </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@tanstack/react-query'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> toast </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'sonner'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> $post </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">api</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">users</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">$post</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">type</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">BodyType</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token maybe-class-name">InferRequestType</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">typeof</span><span class="token plain"> $post</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token string" style="color:hsl(119, 34%, 47%)">'json'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">type</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">ResponseType</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token maybe-class-name">InferResponseType</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">typeof</span><span class="token plain"> $post</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token string" style="color:hsl(119, 34%, 47%)">'data'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">useUserCreate</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token generic-function function" style="color:hsl(221, 87%, 60%)">useMutation</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token generic-function generic class-name" style="color:hsl(35, 99%, 36%)">ResponseType</span><span class="token generic-function generic class-name punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token generic-function generic class-name" style="color:hsl(35, 99%, 36%)"> Error</span><span class="token generic-function generic class-name punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token generic-function generic class-name" style="color:hsl(35, 99%, 36%)"> BodyType</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    mutationKey</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token string" style="color:hsl(119, 34%, 47%)">'create-user'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">mutationFn</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">json</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> data </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">$post</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> json </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">json</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> data</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">onSuccess</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      toast</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">success</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'User created successfully'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">onError</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">error</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      toast</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">error</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">error</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">message</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>在 <code>app/users/page.tsx</code> 中的使用</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token string" style="color:hsl(119, 34%, 47%)">'use client'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> useUserCreate </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@/features/users/use-user-create'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">UsersPage</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> mutate</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> isPending </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">useUserCreate</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">handleSubmit</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">e</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access maybe-class-name">FormEvent</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token maybe-class-name">HTMLFormElement</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    e</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">preventDefault</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> formData </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">FormData</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">e</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">currentTarget</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> name </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> formData</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'name'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">as</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> email </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> formData</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'email'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">as</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token function" style="color:hsl(221, 87%, 60%)">mutate</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> name</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> email </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag" style="color:hsl(5, 74%, 59%)">form</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">onSubmit</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">handleSubmit</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag" style="color:hsl(5, 74%, 59%)">div</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag" style="color:hsl(5, 74%, 59%)">label</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">htmlFor</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">name</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text">Name:</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag" style="color:hsl(5, 74%, 59%)">label</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag" style="color:hsl(5, 74%, 59%)">input</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">type</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">text</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">id</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">name</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">name</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">name</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag" style="color:hsl(5, 74%, 59%)">div</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag" style="color:hsl(5, 74%, 59%)">div</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag" style="color:hsl(5, 74%, 59%)">label</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">htmlFor</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">email</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text">Email:</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag" style="color:hsl(5, 74%, 59%)">label</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag" style="color:hsl(5, 74%, 59%)">input</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">type</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">email</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">id</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">email</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">name</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">email</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag" style="color:hsl(5, 74%, 59%)">div</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag" style="color:hsl(5, 74%, 59%)">button</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">type</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">submit</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">'</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">disabled</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">isPending</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">        Create User</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag" style="color:hsl(5, 74%, 59%)">button</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag" style="color:hsl(5, 74%, 59%)">form</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="openapi-文档">OpenAPI 文档<a href="https://kuizuo.me/blog/nextjs-with-hono#openapi-%E6%96%87%E6%A1%A3" class="hash-link" aria-label="OpenAPI 文档的直接链接" title="OpenAPI 文档的直接链接">​</a></h2>
<blockquote>
<p>这部分我已经弃坑了，没找到一个很好的方式为 Hono 写 OpenAPI 文档。不过对于 TS 全栈开发者，似乎也没必要编写 API 文档（接口自给自足），更何况还有 RPC 这样的黑科技，不担心接口的请求参数与响应接口。</p>
</blockquote>
<p>如果你真要写，那我说说几个我遇到的坑，也是我弃坑的原因。</p>
<p>首先就是写法上，你需要将所有的 Hono 替换成 OpenAPIHono (来自 <a href="https://www.npmjs.com/package/@hono/zod-openapi" target="_blank" rel="noopener noreferrer">@hono/zod-openapi</a>， 其中 zod 实例 z 也是)。以下是官方的<a href="https://hono.dev/examples/zod-openapi" target="_blank" rel="noopener noreferrer">示例代码</a>，我将其整合到一个文件内</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> createRoute</span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">OpenAPIHono</span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token imports"> z </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@hono/zod-openapi'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> swaggerUI </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@hono/swagger-ui'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> app </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">OpenAPIHono</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token maybe-class-name">ParamsSchema</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">object</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> z</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">min</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">3</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">openapi</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      param</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'id'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">in</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'path'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      example</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'123'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token maybe-class-name">UserSchema</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> z</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">object</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">openapi</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> example</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'123'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">openapi</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> example</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'John Doe'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">openapi</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'User'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> route </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">createRoute</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  method</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'get'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  path</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'/api/users/{id}'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  request</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    params</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token maybe-class-name">ParamsSchema</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  responses</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token number" style="color:hsl(35, 99%, 36%)">200</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      content</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token string-property property" style="color:hsl(5, 74%, 59%)">'application/json'</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          schema</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token maybe-class-name">UserSchema</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      description</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'Retrieve the user'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">openapi</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">route</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> id </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">req</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">valid</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'param'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// 逻辑代码...</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> user </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    id</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'Ultra-man'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> c</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">json</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>从上述代码的可读性来看，第一眼你很难看到清晰的看出这个接口到底是什么请求方法、请求路径，并且在写法上需要使用 <code>.openapi</code> 方法，传入一个由 createRoute 所创建的 router 对象。并且写法上不是在原有基础上扩展，已有的代码想要通过<a href="https://apifox.com/blog/api-first-api-design-first-or-code-first/" target="_blank" rel="noopener noreferrer">代码优先</a>的方式来编写 OpenAPI 文档将要花费不小的工程，这也是我为何不推荐的原因。</p>
<p>定义完接口(路由)之后，只需要通过 app.doc 方法与 swaggerUI 函数，访问 /api/doc 查看 OpenAPI 的 JSON 数据，以及访问 /api/ui 查看 Swagger 界面。</p>
<div class="language-jsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-jsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword module" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> swaggerUI </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@hono/swagger-ui'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">doc</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'/api/doc'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token literal-property property" style="color:hsl(5, 74%, 59%)">openapi</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'3.0.0'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token literal-property property" style="color:hsl(5, 74%, 59%)">info</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token literal-property property" style="color:hsl(5, 74%, 59%)">version</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'1.0.0'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token literal-property property" style="color:hsl(5, 74%, 59%)">title</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'Demo API'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'/api/ui'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">swaggerUI</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:hsl(5, 74%, 59%)">url</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'/api/doc'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024%2F0930171730-imageundefined4.png" alt="image.png" class="img_OpE3"></p>
<p>从目前来看，OpenAPI 文档的生成仍面临挑战。我们期待 Hono 未来能推出一个功能，可以根据 app 下的路由自动生成接口文档（相关<a href="https://github.com/honojs/hono/issues/2970" target="_blank" rel="noopener noreferrer">Issue</a>已存在）。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="仓库地址">仓库地址<a href="https://kuizuo.me/blog/nextjs-with-hono#%E4%BB%93%E5%BA%93%E5%9C%B0%E5%9D%80" class="hash-link" aria-label="仓库地址的直接链接" title="仓库地址的直接链接">​</a></h2>
<p>附上本文中示例 demo 仓库链接（这个项目就不搞线上访问了）</p>
<p><a href="https://github.com/kuizuo/nextjs-with-hono" target="_blank" rel="noopener noreferrer">https://github.com/kuizuo/nextjs-with-hono</a></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="后记">后记<a href="https://kuizuo.me/blog/nextjs-with-hono#%E5%90%8E%E8%AE%B0" class="hash-link" aria-label="后记的直接链接" title="后记的直接链接">​</a></h2>
<p>其实我还想写写 Auth、DB 这些服务集成的(这些都在我实际工作中实践并应用了)，或许是太久未写 Blog 导致手生了不少，这篇文章也是断断续续写了好几天。后续我将会出一版完整的我个人的 Nextjs 与 Hono 的最佳实践模版。</p>
<p>也说说我为什么会选用 Hono.js 作为后端服务, 其实就是 Next.js 的 API Route 实在是太难用了，加之轻量化，你完全可以将整个 Nextjs + Hono 服务部署在 Vercel 上，并且还能用上 <a href="https://vercel.com/docs/functions" target="_blank" rel="noopener noreferrer">Edge Functions</a> 的特性。(就是有点小贵)</p>
<p>但不过从我的 Nest.js 开发经验来看（也可能是习惯了 Spring Boot 那套三层架构开发形态），总觉得 Hono 差了点意思，说不出来的体验，可能这就是所谓的全栈框架的开发感受吧。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>nextjs</category>
            <category>honojs</category>
        </item>
        <item>
            <title><![CDATA[记 · 在 AI 公司入职一个月的体验与感悟]]></title>
            <link>https://kuizuo.me/blog/experience-of-an-ai-company</link>
            <guid>https://kuizuo.me/blog/experience-of-an-ai-company</guid>
            <pubDate>Mon, 10 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[这篇文章分享了作者在一家 AI 公司入职一个月的心得和体会，包括工作中的挑战与成长。]]></description>
            <content:encoded><![CDATA[<div class="theme-admonition theme-admonition-success admonition_fh9h alert alert--success"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>前文提醒</div><div class="admonitionContent_oz3Y"><p>作者已于 9 月 30 日离职，以下内容只发生于入职后的 1 个月，之后就过着如同牛马般的生活，日子又开始没有盼头了😔。</p></div></div>
<p>已经在一家 AI 公司入职了一个月，对坐班有些厌恶的我，没想到有一天也会开始通勤打卡。而经历了这一个月的工作，我对坐班的态度有所转变，开始理解这种工作方式对我的意义。是时候分享入职这期间的工作内容与感受。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="背景">背景<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="背景的直接链接" title="背景的直接链接">​</a></h2>
<p>直入正题，先说职位背景。该职位的技术要求大致如下，仅做参考。</p>
<div class="language-javascript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-javascript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">## 任职要求 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token number" style="color:hsl(35, 99%, 36%)">1.</span><span class="token plain"> 本科及以上学历，计算机科学、软件工程等相关专业</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> 硕士优先； </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token number" style="color:hsl(35, 99%, 36%)">2.</span><span class="token plain"> 扎实的 </span><span class="token constant" style="color:hsl(35, 99%, 36%)">HTML</span><span class="token plain">、</span><span class="token constant" style="color:hsl(35, 99%, 36%)">CSS</span><span class="token plain">、JavaScript </span><span class="token function" style="color:hsl(221, 87%, 60%)">基础</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">vanilla</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> 功底 ； </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token number" style="color:hsl(35, 99%, 36%)">3.</span><span class="token plain"> 熟练使用 </span><span class="token maybe-class-name">React、React</span><span class="token plain"> </span><span class="token maybe-class-name">Native</span><span class="token plain"> 和 </span><span class="token maybe-class-name">Next</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">js</span><span class="token plain"> 进行前端开发 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token number" style="color:hsl(35, 99%, 36%)">4.</span><span class="token plain"> 了解前端性能优化技术，如代码压缩、懒加载等 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token number" style="color:hsl(35, 99%, 36%)">5.</span><span class="token plain"> 熟悉前端工程化工具 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token number" style="color:hsl(35, 99%, 36%)">6.</span><span class="token plain"> 具备良好的问题解决能力和团队协作精神 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token number" style="color:hsl(35, 99%, 36%)">7.</span><span class="token plain"> 熟练阅读英文技术文档 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token number" style="color:hsl(35, 99%, 36%)">8.</span><span class="token plain"> 有优异前端项目开发经验者优先 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">## 加分项： </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> 贡献开源社区 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> 有 </span><span class="token constant" style="color:hsl(35, 99%, 36%)">AI</span><span class="token plain"> 相关项目经验。 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> 有前端性能优化和 </span><span class="token constant" style="color:hsl(35, 99%, 36%)">SEO</span><span class="token plain"> 优化经验。 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">有良好的产品思维和设计</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token constant" style="color:hsl(35, 99%, 36%)">UI</span><span class="token operator" style="color:hsl(221, 87%, 60%)">/</span><span class="token constant" style="color:hsl(35, 99%, 36%)">UX</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain">意识。 </span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> 有同理心思维。</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> 具有一定的审美感。</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>很贴合国外主流的技术栈（至于为何，看后文便知），比较巧的是，我的 Web 全栈学习路线就是偏国外的技术栈。因此在技术栈上，这家公司是我喜欢的，恰巧又是 AI 开发，能让我尝试到一些前沿技术，也正好是想我折腾的。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="求职经历">求职经历<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E6%B1%82%E8%81%8C%E7%BB%8F%E5%8E%86" class="hash-link" aria-label="求职经历的直接链接" title="求职经历的直接链接">​</a></h2>
<p>我是 Boss 直聘上找的（这里没给 boss 直聘打广告，我甚至还是第一次使用 boss 直聘），我有想过找人内推，但由于家庭因素被限定在福州这座城市，而内推的所在的城市往往都是那些一线城市，加上我的八股文和算法很不过关（我也很不情愿刷），到时候面试那关估计也不乐观。</p>
<p>因此就在 Boss 上碰碰运气，也顺带体验一下新人都是怎么找工作的。</p>
<p>从五一的时候开始准备简历和项目，在5号开始投简历，投递简历一关我是直接怼着工作经验1-3年的来投，而不是投应届或实习岗。因为我确实有一些工作经验，只不过不是正常的坐班打卡的形式，这在之前的博客中有说到。</p>
<p>在这期间共投了20多家，基本都是已读不回，就更别说投递简历了。后来我才了解到，原来 HR 回复消息是要花钱的，发布一个岗位也是。</p>
<p>唯一回复的还是我现在入职的这家，而且我还投了两份过去，一份是给 HR 的（没回），一份是给技术 leader 的（leader 回了）。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0611021351-Untitled.png" alt="Untitled" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="面试被鸽">面试被鸽<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E9%9D%A2%E8%AF%95%E8%A2%AB%E9%B8%BD" class="hash-link" aria-label="面试被鸽的直接链接" title="面试被鸽的直接链接">​</a></h3>
<p>可能是由于当时这个岗位急招的原因，在 boss 直聘上也没多说什么，leader 就约明早 11 点来公司现场初步面试聊天一下。这期间还发生了一个小变故，我到公司了，可联系不上面试官，打了微信电话也无果。待了10来分钟后我就走了，等了约一个小时都没信息，那我大概率是被鸽了，还不提前和我通知一声，然后在boss上留下了这句评价🥲。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0611021351-1a520a5a-c3b9-4049-bfcf-825113aa7b2c.png" alt="Untitled" class="img_OpE3"></p>
<p>初入职场，初次面试就这种情况，说真的我当时都有点心灰意冷了，我猜想是不是因为有其他合适的人选，于是就不招我了，就连信息也不给我打一个招呼，相当于把我拉黑似得。随后我就到附近的麦当劳花了 10 元的套餐安慰了一下自己，麦！</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="开始面试">开始面试<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E5%BC%80%E5%A7%8B%E9%9D%A2%E8%AF%95" class="hash-link" aria-label="开始面试的直接链接" title="开始面试的直接链接">​</a></h3>
<p>直到到下午一点多的时候，面试官回复我说当时他们在开会，期间不让携带电子设备。早上就当一面过了，问我下午有没有时间，直接二面技术面(code test)过了就直接拿offer。</p>
<p>这时我才知道，原来早上也仅仅只是我的猜想，但我还是有点不想去了，心情有点不太愉悦，但想了想也懒得计较了，过去就当聊天罢了。到了下午面试问的就偏前端基础、八股文那些问题，其实我回答的巨烂，确实也没好好刷题，也不喜欢刷题，就面试了。自己写代码是由业务环境下驱动的，并从中寻求最佳实践。但好在我的技术面是比较广的，很多前沿的前端相关的工具库或多或少都使用过，也能侃侃而谈，加上个人 blog 和 github 这两个大加分项。就进入到了一个代码考核测试，不限框架，不限规则，使用公司的电脑打开 codesandbox 写一个todo list，前提是不使用任何 AI 工具。</p>
<p>这不正好到了我的强项，之前学某个框架的时候，不知道写什么demo，就写 todo list 来练手😂。恰好这次我就使用 next.js app router + Tailwindcss 的模版并且使用 form 标签的 action 和 use server 来实现新增功能。 能体现出我有在使用 next.js，而且用上了一些新特性，就拿到 offer 了。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0611021351-03100cc0-2b0a-4049-82ed-58cc46ac1717.png" alt="1000047893.jpg" class="img_OpE3"></p>
<p>听完之后是不是莫名的感觉这个 offer 拿的好像有点莫名其妙的感觉😂，不管怎么样结果是好的就行了。</p>
<p>不过拿到 offer 后，我并没有选择马上入职，经历了一次被鸽的经历，对该公司的印象带有一些怀疑。其次就是这是一家初创 AI 公司，规模不大，从应届生找工作的角度，第一份正式的工作的起点很关键，如果能直接进大厂，后续跳槽到其他公司大概率也不成问题。</p>
<p>但在当地我投递了 20 多家已读不回的情况下，加上这份已有的 offer 不等人（急招），加上我家里人给我推荐的工作内容我并不是很满意，于是思考了两天，最终还是选择入职了这家公司。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="薪资">薪资<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E8%96%AA%E8%B5%84" class="hash-link" aria-label="薪资的直接链接" title="薪资的直接链接">​</a></h3>
<p>比较令我差异的是我与企业签订的直接劳动合同，可能是因为我直接投递 1-3 年的工作经验，但我此时的身份还是应届生，按理来说我应该是签订实习合同后，转正再签劳动合同，<del>难道说我已经提前转正了？</del>。不过也好，这样和学校的三方协议都可以不用签了，直接给劳动合同便可。</p>
<p>试用期 3 个月，薪资打 8 折。薪资在我当地还算 ok，但对于我而言并不理想。可能是会的比较多(<del>全栈</del>？全干！)，加上曾经赚过比这还高上许多的薪资，从内心的角度多少是有些不平衡。不过目前还是试用期，薪资这方面后续也能再谈。</p>
<p>接下来尤为重要的上班体验才是让我觉得没后悔入职这家公司。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="上班体验">上班体验<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E4%B8%8A%E7%8F%AD%E4%BD%93%E9%AA%8C" class="hash-link" aria-label="上班体验的直接链接" title="上班体验的直接链接">​</a></h2>
<p>介绍一下公司部门的办公工具</p>
<p>办公管理：企业微信</p>
<p>团队协作：Slack</p>
<p>任务看板：Trello</p>
<p>代码仓库：Github</p>
<p>代码托管：Vercel</p>
<p>视频会议：Zoom</p>
<p>你会发现除了企业微信，其他的应用都是国外的。怎么看都不是一家国内的企业吧，这是因为我部门的 Leader 是海外留学的，这也就不难理解工具是国外应用，技术栈选型是 React 生态了。</p>
<p>入职的第一周部门开了个小会，就是简单介绍了一下部门的任务职责，每个成员自我介绍。重点是提供一个优质的学习环境，像是技术书籍，电子设备，UI 模版或是技术会议的门票等费用，只要对部门有利，能提升自己，都可以找他报销。</p>
<p>我已经找 Leader 报销了个 magic ui pro，大约 420 块，直接找财务刷卡，付款的感觉是真爽，我是真爱了🥰。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0611021351-Untitled%201.png" alt="Untitled" class="img_OpE3"></p>
<p>几天后，公司来了一个阿里做 B 端低代码开发的同事，也是负责前端开发，这不，我可以间接和这个老哥那学习大厂相关经验，我还正愁着没大厂相关的经验😄。</p>
<p>我询问他来这家公司的原因，他说被裁了，在家接外包一年了，不稳定就准备找工作，恰好这家公司急招，于是就来了。</p>
<div class="theme-admonition theme-admonition-warning admonition_fh9h alert alert--warning"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>补</div><div class="admonitionContent_oz3Y"><p>端午节后，这位老哥提离职了，原因的话我就不具体说了，可能是因为年龄大了，不适合坐班了。虽然早有预感，但还是有点不舍。因为现在部门的前端重任都在我这了😭</p></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="团建">团建<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E5%9B%A2%E5%BB%BA" class="hash-link" aria-label="团建的直接链接" title="团建的直接链接">​</a></h3>
<p>在我入职的第一周周末 Leader 为整个部门安排团建，由于这个部门成立不到 2 周，来的都是新成员，让我们自己组个局，去外面吃个饭。</p>
<p>也是在团建的时候了解到同事的履历一个个都不简单，有 985 的，有海外留学的，有在阿里、网易待过的，还有我这不堪回首的经历 🤡。</p>
<p>后面原定在 61 安排整个公司的团建，但由于天气和周末时间去的人少的因素而取消了，这我就不多说了。</p>
<p>端午之后的第一个工作日的中午，补过端午节部门聚餐的，这我也不多说了。就是怎么感觉这频率有点不太对，然后实际项目产出也还停留在 Spring 1 的阶段，让我有些不自在。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="福利">福利<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E7%A6%8F%E5%88%A9" class="hash-link" aria-label="福利的直接链接" title="福利的直接链接">​</a></h3>
<p>部门每个月都会定一个最佳员工奖，我很荣幸获得部门本月的最佳员工，也感谢部门成员的认可，奖励是 300 元奖金或一日自由假。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0611021351-Untitled%202.png" alt="Untitled" class="img_OpE3"></p>
<p>甚至还有一张奖状，就是这奖状怎么有点像给小学生似的。（事后我才了解到这奖状还是用打印机打印的😂）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0611021351-0da32f2a-c077-42c9-8a57-fff9f259187e.png" alt="Untitled" class="img_OpE3"></p>
<p>目前我已经能感受到最大的福利就是那个 magicui 动效库的模版，当然了，这个是要给公司的官网用上的，我也是蹭公司的福，给自己的站点用上了这个动效库。</p>
<p>此外像节日福利，如这次端午节，就是聚餐和发粽子，这也就没什么好说的。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="通勤">通勤<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E9%80%9A%E5%8B%A4" class="hash-link" aria-label="通勤的直接链接" title="通勤的直接链接">​</a></h3>
<p>公司距离我租房的地方只有 2 公里，每日的通勤总时间大约 40 分钟，早上大约 8 点起床，我通常坐公交车到公司附近的早餐店吃个早饭，吃完差不多 8 点 40分~50 分。中午外卖就不说了。下午下班从公交车和走路做个选择，吃完饭回到家。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="黑客松">黑客松<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E9%BB%91%E5%AE%A2%E6%9D%BE" class="hash-link" aria-label="黑客松的直接链接" title="黑客松的直接链接">​</a></h3>
<p>黑客松(hackathon)，也称编程马拉松比赛。我是第一次听说过这个词，Leader 给定两个选题一个是打造某市地铁智能出行，另一个是给某商场的提供贴心的购物体验，发挥自己近一个月所学的知识，去创造一个供用户使用的 AI 程序，月底交付，奖金 3000 元/小组，抽签分组。我们当时部门有个人提了一嘴，要不我们两小组自己商量一下，把奖金平分得了😂。</p>
<p>不过对于这个行为，我个人认为目的是为了激励员工之间协同合作，但同时也免不了技术上的内耗，毕竟这个比赛不是我们的主要工作内容。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="工作内容">工作内容<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E5%B7%A5%E4%BD%9C%E5%86%85%E5%AE%B9" class="hash-link" aria-label="工作内容的直接链接" title="工作内容的直接链接">​</a></h2>
<p>我想肯定有很多人对 AI 开发的刻板印象是要会大模型开发，会懂得微调，会懂得人工智能算法。这个想法也没错，但从开发 AI 应用的角度，其实蛮需要前端的，尤其是会全栈框架的前端。</p>
<p>这里我不得不惊叹 next.js 的生态，很多 AI 相关的例子可以直接从 Vercel 的 <a href="https://vercel.com/templates?utm_source=next-site&amp;utm_medium=navbar&amp;utm_campaign=next_site_nav_templates&amp;framework=next.js&amp;type=ai" target="_blank" rel="noopener noreferrer">AI Template</a> 下学习，预览是否有你需要的功能，Clone 到本地，然后运行项目，对某些部分进行更改。搭建 AI 应用也是异常的快。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="仿-ai-sdk-网站效果">仿 <a href="https://sdk.vercel.ai/prompt" target="_blank" rel="noopener noreferrer">AI SDK</a> 网站效果<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E4%BB%BF-ai-sdk-%E7%BD%91%E7%AB%99%E6%95%88%E6%9E%9C" class="hash-link" aria-label="仿-ai-sdk-网站效果的直接链接" title="仿-ai-sdk-网站效果的直接链接">​</a></h3>
<p>Leader 下发的一个任务，入职的前两周主要让我熟悉一些怎么使用 next.js 配合 vercel 的 ai sdk 来开发 AI 应用，如怎么调用 openai 的模型，实现一个 ai chatbot。给定了一个任务就是仿造 <a href="https://sdk.vercel.ai/prompt" target="_blank" rel="noopener noreferrer">AI SDK</a>，由于该项目没有开源，自然就只能另辟蹊径。</p>
<p>首先就是仿造页面了，这个作为前端开发，实现起来也算容易，更何况这个这个页面的样式使用 Tailwindcss 编写，直接通过审查元素仿造就行了。</p>
<p>其次在功能实现上，ai sdk 文档都提供了非常完善的解决方案，照着文档将代码稍微改写一下便可，具体的细节就不演示了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="官网首页">官网首页<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E5%AE%98%E7%BD%91%E9%A6%96%E9%A1%B5" class="hash-link" aria-label="官网首页的直接链接" title="官网首页的直接链接">​</a></h3>
<p>两周后开始正式项目开发了，首当其冲的就是官网页。</p>
<p>这里当时 Leader 问我有没有用过 Gatsbyjs，要用这个框架搭建一个官网。我表明我没用过，但我提了一嘴如果要搭建偏内容向的网站，可以考虑 Astro，我愿意折腾一番（我也一直想学 Astro 的）。不过最终在开发时间和成本的商讨下还是选择使用 next.js 来搭建，leader 还顺带给我推荐了一个动效库 magicui，叫我看看里面的案例，看看能不能给官网加点动效。 之后就有了上文提到报销 magicui 的事。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="rag-bot">Rag bot<a href="https://kuizuo.me/blog/experience-of-an-ai-company#rag-bot" class="hash-link" aria-label="Rag bot的直接链接" title="Rag bot的直接链接">​</a></h3>
<p>篇幅实在有限，有关 <a href="https://aws.amazon.com/cn/what-is/retrieval-augmented-generation/" target="_blank" rel="noopener noreferrer">RAG</a> 的不做过多解释，它可以让你的 AI 应用更具有权威性，让数据的来源可靠，而非胡乱生成数据。</p>
<p>RAG 的基本流程就是：</p>
<ol>
<li>用户输入提问</li>
<li>检索：根据用户提问对 向量数据库 进行相似性检测，查找与回答用户问题最相关的内容</li>
<li>增强：根据检索的结果，生成 prompt。 一般都会涉及 “仅依赖下述信息源来回答问题” 这种限制 llm 参考信息源的语句，来减少幻想，让回答更加聚焦</li>
<li>生成：将增强后的 prompt 传递给 llm，返回数据给用户</li>
</ol>
<p>在这个应用开发中，借鉴了 <a href="https://github.com/datastax/ragbot-starter" target="_blank" rel="noopener noreferrer">ragbot-starter</a> 这个开源项目，同时向量数据库选用 datastax 公司的Astra DB。</p>
<p>恰好在开发这个应用的期间，我也正好在学习 Langchain.js，所以在数据处理这部分有点得心应手，目前应用还只停留在处理本地文件或用户上传的文件，只需要配置各种 <a href="https://js.langchain.com/v0.2/docs/integrations/document_loaders/file_loaders/" target="_blank" rel="noopener noreferrer">File Loader</a> 便可。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="使用-rn-实现-chatbot">使用 RN 实现 chatbot<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E4%BD%BF%E7%94%A8-rn-%E5%AE%9E%E7%8E%B0-chatbot" class="hash-link" aria-label="使用 RN 实现 chatbot的直接链接" title="使用 RN 实现 chatbot的直接链接">​</a></h3>
<p>先看 Gif 效果。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0612203208-demo.gif" alt="demo" class="img_OpE3"></p>
<p>第一次用 Screen Studio，显示的不是很好，还请见谅，主要就是实现一个流式文本效果。</p>
<p>这里简单说下怎么实现的，就用  <a href="https://github.com/mrzachnugent/react-native-reusables" target="_blank" rel="noopener noreferrer">react-native-reusables</a> 的模版(React native 的 Shadcn/ui) + <a href="https://github.com/zerodays/react-native-gen-ui" target="_blank" rel="noopener noreferrer">react-native-gen-ui</a> 实现的，不过后者的功能比较单一，后续估计要改代码了。代码就不贴了，我怕涉嫌代码泄露（其实已经泄露差不多了）。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="收获">收获<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E6%94%B6%E8%8E%B7" class="hash-link" aria-label="收获的直接链接" title="收获的直接链接">​</a></h2>
<p>要我说最大的收获不是遇到一个氛围不错的公司，遇到一个好 leader，也不是接触 AI 开发从中学到了什么，更不是增进了我的技术栈。而是让我养成良好的习惯，开始正常一日三餐，开始作息规律，开始将工作与生活分离，身体状态也渐渐好了起来。</p>
<p>下图为 5 月的生物作息，基本都保持 0 点前入睡。（不过在我写这篇文章的时候已经两点了🥱）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0611021351-Untitled%203.png" alt="Untitled" class="img_OpE3"></p>
<p>过去几年内我的作息与饮食都非常糟糕，能明显的感觉到状态有所下滑，编写代码的效率和能力也明显不如以前，有些力不从心。今年都快过去一半了，而我仅仅完成了2篇博文的写作，文章的输出效率明显不行😮‍💨。</p>
<p>在半年前我对自己当时的<a href="https://kuizuo.me/blog/2023-year-end-summary#%E7%8E%B0%E7%8A%B6">现状</a>做了个分析，幻想着坐班或许能改变我当下的现状。如今经历了这一个月的坐班生活，可能是因为坐班而改变，也可能是公司的氛围，不管是那种，让我跳出我原有舒适区，重新拾起对新颖事物的兴趣，重新点燃学习某个技术的热情，重新找回了自我。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="结尾">结尾<a href="https://kuizuo.me/blog/experience-of-an-ai-company#%E7%BB%93%E5%B0%BE" class="hash-link" aria-label="结尾的直接链接" title="结尾的直接链接">​</a></h2>
<p>在我还没找工作之前，从我几个同届毕业的同学和网友的反馈得知今年的就业环境异常险峻。不仅如此，我还在网络上看到了大量工作者对自身工作的抱怨与不满，这些现象让我在工作前让我对未来的就业前景感到了一些不安。</p>
<p>当我亲身入局感受一番，也不禁开始低声叹气。开始思考是什么原因导致了如今大环境不好的现象，人为的制造就业焦虑，还是当下现实本就如此。当我跳出思考，回到现实难道环境好就一定挣钱多，环境差就一定挣钱少吗？社会似乎并不是这么简单的等式。</p>
<p>我逐渐意识到，无论大环境如何，每个人的努力和选择仍是至关重要。在面对不确定性和挑战时，保持学习和进步的态度，以及寻找自己的核心竞争力，才是应对困境的关键。</p>
<p>真正的职业安全感并不完全来自于外部环境，而是来自于我们自身不断提升的能力和适应变化的灵活性。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>经历</category>
            <category>AI</category>
            <category>工作</category>
            <category>记录</category>
        </item>
        <item>
            <title><![CDATA[React Native 开发心得分享]]></title>
            <link>https://kuizuo.me/blog/react-native-develop-experience</link>
            <guid>https://kuizuo.me/blog/react-native-develop-experience</guid>
            <pubDate>Tue, 14 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[最近研究了一下 React Native(简称RN)，并用它作为毕设项目（一个仿小红书的校园社交应用）。经过一段时间的折腾，对 RN 生态有了一些了解，是时候可以分享一些心得了。]]></description>
            <content:encoded><![CDATA[<p>最近研究了一下 React Native(简称RN)，并用它作为毕设项目（一个仿小红书的校园社交应用）。经过一段时间的折腾，对 RN 生态有了一些了解，是时候可以分享一些心得了。</p>
<p>代码仓库： <a href="https://github.com/kuizuo/youni" target="_blank" rel="noopener noreferrer">https://github.com/kuizuo/youni</a></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="为什么是-rn-而不是-flutter">为什么是 RN 而不是 Flutter？<a href="https://kuizuo.me/blog/react-native-develop-experience#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%98%AF-rn-%E8%80%8C%E4%B8%8D%E6%98%AF-flutter" class="hash-link" aria-label="为什么是 RN 而不是 Flutter？的直接链接" title="为什么是 RN 而不是 Flutter？的直接链接">​</a></h2>
<p>很简单，就是技术栈问题。从开发角度而言，尤其还是对于前端开发人员，会 JS 且搞过 React ，那 RN 上手就十分友好，最起码有关 React 社区的逻辑库或状态库是可以使用的。</p>
<p>虽说 Flutter 的性能是会比 RN 好上不少，但抛开需求不谈，与其比性能不如比开发速度。很显然开发 RN 的效率比 开发 Flutter 高上不少。况且真在意性能的话，那多半就不会考虑跨平台技术了，而是直接考虑原生开发了。</p>
<p>再从需求考量，我所编写的应用更偏向于内容展示的 app，而不是编写一个手机电池监控或者内存监控的app，如果是后者，那这时选择任何跨平台开发都没有意义，像这些系统级别的API在跨平台开发基本不太可能实现的。</p>
<p>对于这两个跨平台技术的选择，应该考虑自身需求、开发成本、技术选型，没有最好的只有最适合的。如果有的选择，谁不会选择原生开发是吧。</p>
<p>但凭我自己接触 RN 以来，国内的 RN 资源甚少，反倒是 Flutter 资源很多，并且从这些相关资料来看，确实 Flutter 优于 RN，但还是那句话，这里就不再过多赘述了。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="是否有必要学-react-native">是否有必要学 react-native？<a href="https://kuizuo.me/blog/react-native-develop-experience#%E6%98%AF%E5%90%A6%E6%9C%89%E5%BF%85%E8%A6%81%E5%AD%A6-react-native" class="hash-link" aria-label="是否有必要学 react-native？的直接链接" title="是否有必要学 react-native？的直接链接">​</a></h2>
<p>先说一个结论：<strong>RN ≠ 原生，别指望会个 react 就能写出靠谱的原生应用。</strong></p>
<p>就从我的开发经历来说，坑是真的多，但好在RN拥有庞大的线上社区，可以找到的几乎所有问题的答案。但国内的社区好像并不是很好，很多问题我都是在国外论坛中解决的。</p>
<p>如果你学习它是为了扩展其他平台的开发能力，那么还是可以学习一番的，会有另一番的收获。但如果学 RN 只是为了避免不用学 android 和 iOS 等原生技术就能写 app，那便不建议学习。抱着这心态的话前期开发可能不明显，但到了后面会踩很多坑，而且两眼一黑，因为你不懂 native 开发。</p>
<p>我的个人评价是 RN 只能作为 H5 手机页面运行在原生移动设备的一种展示形态。虽然本质不是，但其所展示的效果如同。RN 不仅仅只是 Web，但也止步于 Web。</p>
<p>顺带吐槽一番，React-Native 项目发布4年多了，还没有 1.0 版本么(¬_¬)</p>
<p>如果你想再继续了解 RN，那么就请往下看。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="expo">Expo<a href="https://kuizuo.me/blog/react-native-develop-experience#expo" class="hash-link" aria-label="Expo的直接链接" title="Expo的直接链接">​</a></h2>
<p>Expo 是基于 React Native 并整合大量常用的 native module(<a href="https://docs.expo.dev/versions/latest/" target="_blank" rel="noopener noreferrer">Expo SDK</a>)，像原生的功能如相册，相机，蓝牙等功能，在 expo 都是直接集成的，相当于封装原生的api，暴露给js调用。因此你不用去了解原生开发的许多知识和坑点，上手即用便可。本地配置好应用所需的环境，就直接直接运行 RN 项目，开发十分方便。</p>
<p>此外 Expo 还提供了 <a href="https://docs.expo.dev/get-started/expo-go/#want-to-understand-how-expo-go-works" target="_blank" rel="noopener noreferrer">Expo Go App</a>，只需要在你的移动端设备中安装它，启动开发服务器并生成 QR 码。在浏览器打开 <a href="https://snack.expo.dev/" target="_blank" rel="noopener noreferrer">snack.expo.dev</a> ，点击 MyDevice，扫码并在 Expo app 中查看。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0514104918-Untitled.png" alt="Untitled" class="img_OpE3"></p>
<p>会自动将该程序实时运行在你的移动端设备，意味着你更改代码也将会同步到Expo go 中。极大程度上提升 RN 的开发体验，尤其是在真机测试阶段。</p>
<p>Expo 官方还贴心的提供了云服务 <a href="https://docs.expo.dev/eas/" target="_blank" rel="noopener noreferrer">Expo Application Services</a> (EAS)，意为这你可以你可以将你的 RN 项目在托管在云服务上，来执行构建与发布等流程。</p>
<p>总之如今开发 RN 请毫不犹豫的使用上 Expo。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="开发中遇到的一些坑点">开发中遇到的一些坑点<a href="https://kuizuo.me/blog/react-native-develop-experience#%E5%BC%80%E5%8F%91%E4%B8%AD%E9%81%87%E5%88%B0%E7%9A%84%E4%B8%80%E4%BA%9B%E5%9D%91%E7%82%B9" class="hash-link" aria-label="开发中遇到的一些坑点的直接链接" title="开发中遇到的一些坑点的直接链接">​</a></h2>
<p>实际开发中所遇到的坑点远不止下述所说，这里只列举几个相对有代表，坑比较深的点。甚至有很多坑都不是前端方面的知识了。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="在-pnpm-下无法启动-android">在 pnpm 下无法启动 Android<a href="https://kuizuo.me/blog/react-native-develop-experience#%E5%9C%A8-pnpm-%E4%B8%8B%E6%97%A0%E6%B3%95%E5%90%AF%E5%8A%A8-android" class="hash-link" aria-label="在 pnpm 下无法启动 Android的直接链接" title="在 pnpm 下无法启动 Android的直接链接">​</a></h2>
<p>错误提示：Error: Unable to resolve module ./nxode_modules/expo/AppEntry</p>
<p>解决方案：在项目根目录创建 <code>.npmrc</code> ，内容如下</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">shamefully</span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain">hoist</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token boolean" style="color:hsl(35, 99%, 36%)">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">node</span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain">linker</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain">hoisted</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>删除 node_modules 与 .expo 文件夹，重新安装依赖即可。</p>
<p>相关链接：<a href="https://github.com/expo/expo/issues/9591#issuecomment-1485871356" target="_blank" rel="noopener noreferrer">https://github.com/expo/expo/issues/9591#issuecomment-1485871356</a></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="样式问题">样式问题<a href="https://kuizuo.me/blog/react-native-develop-experience#%E6%A0%B7%E5%BC%8F%E9%97%AE%E9%A2%98" class="hash-link" aria-label="样式问题的直接链接" title="样式问题的直接链接">​</a></h3>
<p>在样式方面与传统的 Web 开发存在一定的区别。在 RN 中有两个主要组件，View 与 Text，可以理解为 Web 的 div 与 span。基本所有的 View 都是 flex 布局，想要让 View 组件占满通常不会使用 width: ’100%’ 或 height: ‘100%’，而是使用 flex: 1，例如一般都会带上这么一个样式。</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">View</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> felx</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">1</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>如果样式问题就只是这样就好了，同一套样式在不同平台上所展示的效果都可能不大一样，尤其使用原生 Web 的样式，哪怕你用 style 编写，在 Web 网页也能成功显示效果，但是在 IOS 与 Android 中绝大多数情况下是不显示的。这会在后面介绍 Tailwindcss 相关库的时候会额外在提到一点。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="文本必须要用-text-包裹">文本必须要用 Text 包裹<a href="https://kuizuo.me/blog/react-native-develop-experience#%E6%96%87%E6%9C%AC%E5%BF%85%E9%A1%BB%E8%A6%81%E7%94%A8-text-%E5%8C%85%E8%A3%B9" class="hash-link" aria-label="文本必须要用 Text 包裹的直接链接" title="文本必须要用 Text 包裹的直接链接">​</a></h2>
<p>如果不怎么做的话，会报错，如果只是这样倒还没什么。重点是错误提示并没有堆栈信息！就如下图所示</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0514104918-Untitled%201.png" alt="Untitled" class="img_OpE3"></p>
<p>这点对于开发体验而言并不友好。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="模拟器无法请求本地-api">模拟器无法请求本地 api<a href="https://kuizuo.me/blog/react-native-develop-experience#%E6%A8%A1%E6%8B%9F%E5%99%A8%E6%97%A0%E6%B3%95%E8%AF%B7%E6%B1%82%E6%9C%AC%E5%9C%B0-api" class="hash-link" aria-label="模拟器无法请求本地 api的直接链接" title="模拟器无法请求本地 api的直接链接">​</a></h3>
<p>由于一开始是在 Web 端进行调试开发的，所以没留意到这个问题，直到切换到安卓模拟器之后发现模拟器无法请求本地后端服务，在IOS 端暂无这问题。因此需要做如下配置：</p>
<p>1、首先将模拟器内网切换到本地。</p>
<p>假设后端 api 地址为 <code>[http://localhost:6001](http://localhost:6001)</code>，正常情况下，开发环境下的调试主机可以通过如下方式获取</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">Constants</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'expo-constants'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> debuggerHost </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token maybe-class-name">Constants</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">expoConfig</span><span class="token operator" style="color:hsl(221, 87%, 60%)">?.</span><span class="token plain">hostUri</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token comment" style="color:hsl(230, 4%, 64%)">// 192.168.123.233:8081</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>接着所要做的就是将 192.168.123.233:8081 替换成我们的目标端口 192.168.123.233:6001</p>
<p>这里以 axios 为例， 先为环境变量添加 <code>EXPO_PUBLIC_API_URL=http://localhost:6001</code>，具体替换的代码如下所示</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> client </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> axios</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">create</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  baseURL</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">getApiUrl</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  timeout</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">5000</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">getApiUrl</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> apiUrl </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> process</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">EXPO_PUBLIC_API_URL</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">replaceLocalhost</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">apiUrl</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">getLocalhost</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">localhost </span><span class="token operator" style="color:hsl(221, 87%, 60%)">!==</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">undefined</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> localhost</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> debuggerHost </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token maybe-class-name">Constants</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">expoConfig</span><span class="token operator" style="color:hsl(221, 87%, 60%)">?.</span><span class="token plain">hostUri</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// 192.168.123.233:8081</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  localhost </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> debuggerHost</span><span class="token operator" style="color:hsl(221, 87%, 60%)">?.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">split</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">':'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">??</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'localhost'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> localhost</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">replaceLocalhost</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">address</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token constant" style="color:hsl(35, 99%, 36%)">PROTOCOL</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'http:'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> localhostRegex </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">RegExp</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token template-string interpolation constant" style="color:hsl(35, 99%, 36%)">PROTOCOL</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token template-string string" style="color:hsl(119, 34%, 47%)">\/\/localhost:</span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'g'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> address</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">replace</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">localhostRegex</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token template-string interpolation constant" style="color:hsl(35, 99%, 36%)">PROTOCOL</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token template-string string" style="color:hsl(119, 34%, 47%)">//</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token template-string interpolation function" style="color:hsl(221, 87%, 60%)">getLocalhost</span><span class="token template-string interpolation punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token template-string interpolation punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token template-string string" style="color:hsl(119, 34%, 47%)">:</span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>2、端口转发</p>
<p>此外还需要执行以下命令转发端口。</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">adb reverse tcp</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token number" style="color:hsl(35, 99%, 36%)">6001</span><span class="token plain"> tcp</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token number" style="color:hsl(35, 99%, 36%)">6001</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此时安卓模拟器便可正常请求本地后端服务的资源，IOS 端并未有该问题。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="组件库的选择">组件库的选择<a href="https://kuizuo.me/blog/react-native-develop-experience#%E7%BB%84%E4%BB%B6%E5%BA%93%E7%9A%84%E9%80%89%E6%8B%A9" class="hash-link" aria-label="组件库的选择的直接链接" title="组件库的选择的直接链接">​</a></h2>
<p>如今在 UI 的选择上，我是毫不犹豫选择 Tailwindcss，在 RN 使用 Tailwindcss 有两个库可以作为选择 <a href="https://github.com/marklawlor/nativewind" target="_blank" rel="noopener noreferrer">nativewind</a> 和 <a href="https://github.com/jaredh159/tailwind-react-native-classnames?tab=readme-ov-file" target="_blank" rel="noopener noreferrer">twrnc</a>。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="nativewind">nativewind<a href="https://kuizuo.me/blog/react-native-develop-experience#nativewind" class="hash-link" aria-label="nativewind的直接链接" title="nativewind的直接链接">​</a></h3>
<p>nativewind 采用 Web 的 className 属性，其用法如同 Web 开发使用 Tailwindcss 的写法，这里便不过多展示了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="twrnc">twrnc<a href="https://kuizuo.me/blog/react-native-develop-experience#twrnc" class="hash-link" aria-label="twrnc的直接链接" title="twrnc的直接链接">​</a></h3>
<p>twrnc 的写法则有些不同，需要通过 tw 包装，然后填写到 style 中，就如下图所示</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">View</span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Text</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'react-native'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports">tw</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'twrnc'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">MyComponent</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">View</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">tw</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">p-4 android:pt-2 bg-white dark:bg-black</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Text</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">tw</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">text-md text-black dark:text-white</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text">Hello World</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Text</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">View</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="theme-admonition theme-admonition-danger admonition_fh9h alert alert--danger"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>重点</div><div class="admonitionContent_oz3Y"><p>但要值得注意的是，由于 RN 的组件样式中并不是完全兼容 Web 端，就比如说你想实现毛玻璃效果，通过 <a href="https://tailwindcss.com/docs/backdrop-blur" target="_blank" rel="noopener noreferrer">backdrop-blur</a> 原子类就可以轻松实现，但是在原生移动端并不能生效，其原因就是原生组件的 View 并没有毛玻璃效果，想要实现则需要使用 expo-blur 这个库。</p></div></div>
<p><strong>事实上有很多 Web 端支持的类，在移动端并不能生效，通常来说只适合用 Tailwindcss 来编写基本的宽高，内外边距等样式。</strong></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="这两个库的区别">这两个库的区别<a href="https://kuizuo.me/blog/react-native-develop-experience#%E8%BF%99%E4%B8%A4%E4%B8%AA%E5%BA%93%E7%9A%84%E5%8C%BA%E5%88%AB" class="hash-link" aria-label="这两个库的区别的直接链接" title="这两个库的区别的直接链接">​</a></h4>
<p>从 Web 开发使用的角度，nativewind 会更好用一些， npm 实际使用量也确实比 twrnc 来的多，但要在一些情况下，比如给<a href="https://www.nativewind.dev/v4/guides/third-party-components" target="_blank" rel="noopener noreferrer">第三方组件更改 props 的样式</a>情况下就会没有 twrnc 那么直观了，例如一些第三方组件有 xxxStyle 属性，例如 contentContainerStyle，这时 twrnc 就方便很多。</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">FlatList</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">tw</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">flex-1</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">contentContainerStyle</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">tw</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">p-4</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>而 nativewind 则繁琐许多，下图例子。</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token comment" style="color:hsl(230, 4%, 64%)">// This component has two 'style' props</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">ThirdPartyComponent</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> style</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> contentContainerStyle</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token spread operator" style="color:hsl(221, 87%, 60%)">...</span><span class="token plain">props </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">FlatList</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">style</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">contentContainerStyle</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">contentContainerStyle</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag spread punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag spread operator" style="color:hsl(221, 87%, 60%)">...</span><span class="token tag spread" style="color:hsl(5, 74%, 59%)">props</span><span class="token tag spread punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token comment" style="color:hsl(230, 4%, 64%)">// Call this once at the entry point of your app</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token function" style="color:hsl(221, 87%, 60%)">remapProps</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token maybe-class-name">ThirdPartyComponent</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  className</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'style'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  contentContainerClassName</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'contentContainerStyle'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token comment" style="color:hsl(230, 4%, 64%)">// Now you can use the component with NativeWind</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">ThirdPartyComponent</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">className</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">p-5</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">contentContainerClassName</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">p-2</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>再者，twrnc 可以使用动态变量，例如在 RN 中经常需要处理安全区域，如下写法在 twrnc 就支持，但 nativewind 则不生效。</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> top </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">useSafeAreaInsets</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">View</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">tw</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">pt-[</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token tag script language-javascript template-string interpolation" style="color:hsl(5, 74%, 59%)">top</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">]</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"> // twrnc 支持</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text"></span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">View</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">className</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">pt-[</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token tag script language-javascript template-string interpolation" style="color:hsl(5, 74%, 59%)">top</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">]</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"> // nativewind 不支持</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="tamagui不推荐">tamagui(不推荐)<a href="https://kuizuo.me/blog/react-native-develop-experience#tamagui%E4%B8%8D%E6%8E%A8%E8%8D%90" class="hash-link" aria-label="tamagui(不推荐)的直接链接" title="tamagui(不推荐)的直接链接">​</a></h3>
<p>我便提一下 tamagui 这个组件库。tamagui 看似很炫酷，但是实际配置的过程异常的繁琐，用起来也没有特别舒服，可以看以下示例代码。</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">XStack</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">flex</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">1</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">justifyContent</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">center</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">alignItems</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">center</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">gap</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">$2</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Button</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">size</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">$3</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">theme</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">active</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">    Active</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Button</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Button</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">size</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">$3</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">variant</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">outlined</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">    Outlined</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Button</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text"></span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">XStack</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>其效果就是一个容器内包含两个按钮，样式编写上则通过 prop 属性来实现，用过 unocss 的 <a href="https://unocss.dev/presets/attributify#attributify-mode" target="_blank" rel="noopener noreferrer">Attributify Mode</a> 应该会有些许熟悉，但还不那么一样。</p>
<p>并且他的主题系统使用极其的怪，采用 $number 的形式来定义尺寸(官方称 token)，重点是宽高和边距采用相同的 token 效果还不一样，贴个图。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0514104918-Untitled%202.png" alt="Untitled" class="img_OpE3"></p>
<p>但他的颜色更是一言难尽了，从 color0 到 color11 的效果就如下图</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0514104918-Untitled%203.png" alt="Untitled" class="img_OpE3"></p>
<p>可能是因为我用惯了 Tailwindcss 那套颜色系统，所以很不能理解这套颜色系统，并且在我实际编写组件的过程也是异常的奇怪。</p>
<p>但最让我想吐槽的是官方还为此提供了一个主题系统配置的生成器网站，但只有 tamagui 的赞助者才能够使用，如果想要自己定义一个主题，就需要配置特别多的文件，总之就是很难用就对了。</p>
<p>顺带在贴一张 Provider 嵌套</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0514171536-0514092451-202405140924689.png" alt="provider" class="img_OpE3"></p>
<p>这里我就不得不提到我为啥一开始选用 tamagui 了(现已迁移到 gluestack-ui)，说实话我是有点后悔的，在一开始选定 UI 库的时候，我是选择 NativeWind 的，但后面无意刷到了 <a href="https://t4stack.com/" target="_blank" rel="noopener noreferrer">T4-stack</a> (算是被他坑了)，而它所用的便是 tamagui，并且一套代码跑 expo 与 next.js。于是便采用相同的项目结构以及 UI 库了。但事实上在我编写的过程中，想要一套代码就能实现跨三端(web,android,ios) 效果并不佳了，这在下一章便会说到。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="gluestack-ui">gluestack-ui<a href="https://kuizuo.me/blog/react-native-develop-experience#gluestack-ui" class="hash-link" aria-label="gluestack-ui的直接链接" title="gluestack-ui的直接链接">​</a></h2>
<p>首先它与 tamagui 相似，也采用 token 的方式来定义尺寸样式，但该库所对标的 token 设计就是Tailwindcss。此外该 UI 提供 NativeWind 的定制方案，意味着你的项目中可以集成了 NativeWind 用 Tailwindcss 的方式编写组件(类似 shadcn/ui)，<strong>并且还在 X 上表示 gluestack-ui + NativeWind 组合就是 React Native 的 shadcn/ui</strong>。</p>
<p>因此我个人是比较看好的，不过目前该库目前还处于 Alpha 阶段，可以持续观望中。这个也是我目前最值得推荐的组件库。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="react-native-和-nextjs-应用程序共享代码">React Native 和 Next.js 应用程序共享代码<a href="https://kuizuo.me/blog/react-native-develop-experience#react-native-%E5%92%8C-nextjs-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E5%85%B1%E4%BA%AB%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="React Native 和 Next.js 应用程序共享代码的直接链接" title="React Native 和 Next.js 应用程序共享代码的直接链接">​</a></h2>
<p>如果你想要在 React Native 和 Next.js 应用程序共享代码(UI，逻辑)，你可以考虑使用 <a href="https://solito.dev/" target="_blank" rel="noopener noreferrer">solito</a>。该库的写法上会更偏向于 next 的写法，举个例子。</p>
<p>比如说 Image 组件在 RN 写法如下</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Image</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'react-native'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Image</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">styles</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token tag script language-javascript property-access" style="color:hsl(5, 74%, 59%)">xxx</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">source</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    uri</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript string" style="color:hsl(119, 34%, 47%)">'https://beatgig.com/image.png'</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>next.js 的写法</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">Image</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'next/image'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Image</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">src</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">https://beatgig.com/image.png</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">width</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">100</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">height</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">100</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>solito 的写法</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">SolitoImage</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'solito/image'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">SolitoImage</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">src</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">https://beatgig.com/image.png</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">height</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">100</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">width</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">100</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>这样 <code>SolitoImage</code> 会判断当前的仓库是 next.js 项目还是 RN 项目对不同的平台进行渲染，以做到同一个组件跨平台的开发，像 Link、useRouter 都是类似用法。</p>
<p>不过当你想要共享代码时，此时就必须得上 monorepo 了，通常目录结构如下图所示，你也可以到<a href="https://github.com/gluestack/solito-head-starter-kit" target="_blank" rel="noopener noreferrer">这个仓库</a>中查看。</p>
<div class="language-shell codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-shell codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── apps</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   ├── expo</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   └── next</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── packages</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   └── app</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│       ├── features</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│       ├── index.ts</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│       ├── layouts</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│       ├── package.json</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│       ├── provider</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│       └── screens</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── turbo.json</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">└── package.json</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>packages/app 存放主要的公共业务代码，在 next 和 expo 中则直接通过 <code>@xxx/app</code> 子包来导入，具体可看代码，这里就不做过多介绍了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="处理平台差异">处理平台差异<a href="https://kuizuo.me/blog/react-native-develop-experience#%E5%A4%84%E7%90%86%E5%B9%B3%E5%8F%B0%E5%B7%AE%E5%BC%82" class="hash-link" aria-label="处理平台差异的直接链接" title="处理平台差异的直接链接">​</a></h3>
<p>不同平台之间必然会存在一定的开发差异，expo 也提供了相应的解决方案，可以通过给文件添加不同的后缀扩展(.web .android .ios) 以在对应平台执行对应文件，官方文档 <a href="https://docs.expo.dev/router/advanced/platform-specific-modules/#platform-specific-extensions" target="_blank" rel="noopener noreferrer">Platform specific extensions</a></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="一些库分享">一些库分享<a href="https://kuizuo.me/blog/react-native-develop-experience#%E4%B8%80%E4%BA%9B%E5%BA%93%E5%88%86%E4%BA%AB" class="hash-link" aria-label="一些库分享的直接链接" title="一些库分享的直接链接">​</a></h2>
<p>这里只会介绍这个库的用途，至于为什么选择这个而不是其他的，不想做过多的篇幅来解释。如果你用过比这更好的库，也可相互交流。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="gorhombottom-sheet"><a href="https://github.com/gorhom/react-native-bottom-sheet" target="_blank" rel="noopener noreferrer">@gorhom/bottom-sheet</a><a href="https://kuizuo.me/blog/react-native-develop-experience#gorhombottom-sheet" class="hash-link" aria-label="gorhombottom-sheet的直接链接" title="gorhombottom-sheet的直接链接">​</a></h3>
<p>底部窗口，效果如图</p>
<p><img decoding="async" loading="lazy" src="https://raw.githubusercontent.com/gorhom/react-native-bottom-sheet/HEAD/preview.gif" alt="https://raw.githubusercontent.com/gorhom/react-native-bottom-sheet/HEAD/preview.gif" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="shopifyflash-list"><a href="https://github.com/Shopify/flash-list" target="_blank" rel="noopener noreferrer">@shopify/flash-list</a><a href="https://kuizuo.me/blog/react-native-develop-experience#shopifyflash-list" class="hash-link" aria-label="shopifyflash-list的直接链接" title="shopifyflash-list的直接链接">​</a></h3>
<p>一个高性能的列表，可替代 RN 的 <a href="https://reactnative.dev/docs/flatlist" target="_blank" rel="noopener noreferrer">FlatList</a>，其中它还支持如下图布局。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0514104918-Untitled%204.png" alt="Untitled" class="img_OpE3"></p>
<p><a href="https://github.com/calintamas/react-native-toast-message" target="_blank" rel="noopener noreferrer">react-native-toast-message</a></p>
<p>toast 消息组件，轻量简单易用。</p>
<p><img decoding="async" loading="lazy" src="https://github.com/calintamas/react-native-toast-message/raw/main/docs/toast.gif" alt="https://github.com/calintamas/react-native-toast-message/raw/main/docs/toast.gif" class="img_OpE3"></p>
<p><a href="https://github.com/software-mansion/react-native-gesture-handler" target="_blank" rel="noopener noreferrer">react-native-gesture-handler</a></p>
<p>如果你觉得所编写的 RN 应用没有触摸反馈效果，那么可能需要尝试使用 这个库。例如，你可以使用 <a href="https://docs.swmansion.com/react-native-gesture-handler/docs/components/buttons/#rectbutton" target="_blank" rel="noopener noreferrer">RectButton</a> 来包装子元素来实现点击按钮波纹反馈效果。如下图所示</p>
<p><img decoding="async" loading="lazy" src="https://docs.swmansion.com/react-native-gesture-handler/gifs/samplebutton.gif" alt="https://docs.swmansion.com/react-native-gesture-handler/gifs/samplebutton.gif" class="img_OpE3"></p>
<p>此外像拖动组件、滑动删除、放大缩小图片等常见的手势操作，总之这个库都可以实现。</p>
<p><a href="https://github.com/software-mansion/react-native-reanimated" target="_blank" rel="noopener noreferrer">react-native-reanimated</a></p>
<p>RN 动画库，没啥好说的。</p>
<p>以上组件库可以说基本必装，能为 RN 应用使用体验提升一个档次。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="一些案例组件分析">一些案例/组件分析<a href="https://kuizuo.me/blog/react-native-develop-experience#%E4%B8%80%E4%BA%9B%E6%A1%88%E4%BE%8B%E7%BB%84%E4%BB%B6%E5%88%86%E6%9E%90" class="hash-link" aria-label="一些案例/组件分析的直接链接" title="一些案例/组件分析的直接链接">​</a></h2>
<p>分享一些我在编写 RN 中的一些案例。该说不说，RN 的生态是真的可以，很多原生的解决办法几乎都有。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="react-navigation"><a href="https://reactnavigation.org/" target="_blank" rel="noopener noreferrer">React Navigation</a><a href="https://kuizuo.me/blog/react-native-develop-experience#react-navigation" class="hash-link" aria-label="react-navigation的直接链接" title="react-navigation的直接链接">​</a></h2>
<p>在这个库你可以实现几乎所有的原生布局，如底部 tabs，左侧抽屉等，expo 是在此基础上进行包装的。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="底部-tabs">底部 Tabs<a href="https://kuizuo.me/blog/react-native-develop-experience#%E5%BA%95%E9%83%A8-tabs" class="hash-link" aria-label="底部 Tabs的直接链接" title="底部 Tabs的直接链接">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://docs.expo.dev/static/images/expo-router/tabs.png" alt="https://docs.expo.dev/static/images/expo-router/tabs.png" class="img_OpE3"></p>
<p>Expo <a href="https://docs.expo.dev/router/advanced/tabs/" target="_blank" rel="noopener noreferrer">自带案例</a>，实现效果也简单，这里不在赘述了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="左侧抽屉">左侧抽屉<a href="https://kuizuo.me/blog/react-native-develop-experience#%E5%B7%A6%E4%BE%A7%E6%8A%BD%E5%B1%89" class="hash-link" aria-label="左侧抽屉的直接链接" title="左侧抽屉的直接链接">​</a></h3>
<p><a href="https://reactnavigation.org/assets/navigators/drawer/drawer.mp4" target="_blank" rel="noopener noreferrer">https://reactnavigation.org/assets/navigators/drawer/drawer.mp4</a></p>
<p>expo 官方所提供的左侧抽屉是带导航的，也就是说你无法同时使用底部选项和左侧抽屉两个布局效果。因此想要同时使用这两种布局，就要使用 <a href="https://reactnavigation.org/docs/drawer-layout" target="_blank" rel="noopener noreferrer">Drawer Layout</a>，这里分享我个人的实现过程。</p>
<p>首先，编写 DrawerContainer 组件，代码如下</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Drawer</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'react-native-drawer-layout'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> useDrawerOpen </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@/atoms/drawer'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">CustomDrawerContent</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'./CustomDrawerContent'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">DrawerContainer</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> children </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> children</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access maybe-class-name">ReactNode</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">open</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> setOpen</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">useDrawerOpen</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Drawer</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">      </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">open</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">open</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">      </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">onOpen</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript function" style="color:hsl(221, 87%, 60%)">setOpen</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript boolean" style="color:hsl(35, 99%, 36%)">true</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">      </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">onClose</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript function" style="color:hsl(221, 87%, 60%)">setOpen</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript boolean" style="color:hsl(35, 99%, 36%)">false</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">      </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">swipeEnabled</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript boolean" style="color:hsl(35, 99%, 36%)">false</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">      </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">renderDrawerContent</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag script language-javascript tag class-name" style="color:hsl(35, 99%, 36%)">CustomDrawerContent</span><span class="token tag script language-javascript tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token tag script language-javascript tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag script language-javascript tag class-name" style="color:hsl(35, 99%, 36%)">CustomDrawerContent</span><span class="token tag script language-javascript tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain">children</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Drawer</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>如果想要定制化左侧菜单就必须使用 CustomDrawerContent，这里贴相关代码</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">DrawerContentScrollView</span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">DrawerItem</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@react-navigation/drawer'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">CustomDrawerContent</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">DrawerContentScrollView</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">      </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">scrollEnabled</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript boolean" style="color:hsl(35, 99%, 36%)">false</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">      </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">contentContainerStyle</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">        flexGrow</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">1</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">      </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token comment" style="color:hsl(230, 4%, 64%)">/* &lt;DrawerItemList {...props} /&gt; */</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">View</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">className</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">flex-1 mx-2</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">DrawerItem</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">          </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">label</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">子项 1</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">          </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">onPress</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">        </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">DrawerItem</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">          </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">label</span><span class="token tag attr-value punctuation attr-equals" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag attr-value" style="color:hsl(119, 34%, 47%)">子项 2</span><span class="token tag attr-value punctuation" style="color:hsl(119, 34%, 47%)">"</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">          </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">onPress</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">        </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">        </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token comment" style="color:hsl(230, 4%, 64%)">/* ... */</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">VStack</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">DrawerContentScrollView</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">  )</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>最后在 app/_layout.tsx 中用 DrawerContainer 包装一下 Stack，如下代码。</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Stack</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'expo-router'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Provider</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@/provider'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">DrawerContainer</span><span class="token imports"> </span><span class="token imports punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@/components/DrawerContainer'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">RootLayout</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Provider</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">DrawerContainer</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Stack</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">          </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">screenOptions</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">            headerShown</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript boolean" style="color:hsl(35, 99%, 36%)">false</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">          </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">        </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Stack</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">DrawerContainer</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">Provider</span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此时就可以用 useDrawerOpen（这里状态库选用 jotai）来控制左侧菜单的展开了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="tabview">TabView<a href="https://kuizuo.me/blog/react-native-develop-experience#tabview" class="hash-link" aria-label="TabView的直接链接" title="TabView的直接链接">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://reactnavigation.org/assets/libraries/tab-view.gif" alt="https://reactnavigation.org/assets/libraries/tab-view.gif" class="img_OpE3"></p>
<p>同样的，这个效果在 <a href="https://reactnavigation.org/docs/tab-view" target="_blank" rel="noopener noreferrer">React Navigation</a> 也是有提供的。但在 expo 中有 react-native-pager-view作为平替，并且更兼容原生，但是 react-native-pager-view 是不支持 Web 端的，因此如何选择就看具体需求了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="固定-header--tab-view">固定 Header + tab view<a href="https://kuizuo.me/blog/react-native-develop-experience#%E5%9B%BA%E5%AE%9A-header--tab-view" class="hash-link" aria-label="固定 Header + tab view的直接链接" title="固定 Header + tab view的直接链接">​</a></h3>
<p>先看一张图，很多 app 都有这种类似的效果。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0514171652-Untitled.mp4" alt="" class="img_OpE3"></p>
<p>这种效果可以使用监听 ScrollY 配合 <a href="https://github.com/software-mansion/react-native-reanimated" target="_blank" rel="noopener noreferrer">react-native-reanimated</a> 动画来实现，如果你不想自己实现也可以看看 <a href="https://react-native-header.codeherence.com/docs/showcase" target="_blank" rel="noopener noreferrer">@codeherence/react-native-header</a>，上图便来自此库。</p>
<p>此外我还留意到 <a href="https://netguru.github.io/sticky-parallax-header/docs/introduction/getting-started" target="_blank" rel="noopener noreferrer">TabbedHeaderPager</a> 这个库（很坑，别用），别看官方 gif 图效果很炫酷，然而实际效果并不达预期，并且十分难用，比如想要更改 tab 样式得像下方这样传递 props 编写。</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;</span><span class="token tag class-name" style="color:hsl(35, 99%, 36%)">TabbedHeaderPager</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">tabTextStyle</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    color</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> theme</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token tag script language-javascript property-access" style="color:hsl(5, 74%, 59%)">color</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">?.</span><span class="token tag script language-javascript method function property-access" style="color:hsl(221, 87%, 60%)">get</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    padding</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">0</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">tabTextActiveStyle</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    backgroundColor</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript string" style="color:hsl(119, 34%, 47%)">'transparent'</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">tabTextContainerStyle</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    padding</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">0</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">tabTextContainerActiveStyle</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    backgroundColor</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript string" style="color:hsl(119, 34%, 47%)">'transparent'</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">tabWrapperStyle</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    paddingVertical</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">0</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">tabUnderlineColor</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">primaryColor</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">tabsContainerStyle</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    backgroundColor</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> bgColor</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    flex</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">1</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    maxWidth</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript maybe-class-name" style="color:hsl(5, 74%, 59%)">Platform</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token tag script language-javascript method function property-access" style="color:hsl(221, 87%, 60%)">select</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">      web</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">200</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    margin</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript maybe-class-name" style="color:hsl(5, 74%, 59%)">Platform</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token tag script language-javascript method function property-access" style="color:hsl(221, 87%, 60%)">select</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">      web</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript string" style="color:hsl(119, 34%, 47%)">'auto'</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">tabsContainerHorizontalPadding</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript maybe-class-name" style="color:hsl(5, 74%, 59%)">Platform</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token tag script language-javascript method function property-access" style="color:hsl(221, 87%, 60%)">select</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    </span><span class="token tag script language-javascript keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">120</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    web</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">0</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">contentContainerStyle</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:hsl(119, 34%, 47%)">=</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">    flex</span><span class="token tag script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag script language-javascript number" style="color:hsl(35, 99%, 36%)">1</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag script language-javascript" style="color:hsl(5, 74%, 59%)">  </span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token tag" style="color:hsl(5, 74%, 59%)"></span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">/&gt;</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="rn-原生开发的感悟">RN 原生开发的感悟<a href="https://kuizuo.me/blog/react-native-develop-experience#rn-%E5%8E%9F%E7%94%9F%E5%BC%80%E5%8F%91%E7%9A%84%E6%84%9F%E6%82%9F" class="hash-link" aria-label="RN 原生开发的感悟的直接链接" title="RN 原生开发的感悟的直接链接">​</a></h2>
<p>在这段的 RN 开发经历，我还有很多 API 还未尝试，有很多开发上的细节没编写到。篇幅有限，未来如果还有机会编写 RN 项目，再做一些分享(我觉得应该不会有了)。</p>
<p>我曾与安卓开发打过两次交道:</p>
<p>一段是在学习安卓逆向的时候，免不了学习一些基础的原生安卓开发的知识。</p>
<p>另一段是在接触自动化开发的时候，看到了 <a href="https://www.wuyunai.com/docs/" target="_blank" rel="noopener noreferrer">Auto.js</a> 这个库， 可以使用 JavaScript 和 Node.js 实现小型的安卓应用（不支持 IOS），更多是使用这个库来编写一些脚本类相关的应用。现在回看该库的文档，不由得开始莫名的感叹。</p>
<blockquote>
<p>Auto.js Pro 移除了自动化测试、图片处理、消息通知等模块，如果你需要实现的是自动化、工作流工具，则不适合 Auto.js Pro。</p>
</blockquote>
<p>在如今内卷的环境下，技术框架变化飞快，文档示例不断完善，服务商们也提供快速搭建应用的模版，又赶上了 AI 热潮，学习一件新东西对于初学者过于容易。随之而来的是开发人员变多，市场需求不足难以满足如此庞大的开发人员，貌似技术对开发人员本身也不是那么的重要？</p>
<p>对于技术人员要如何破局，或许是每位程序员的最值得思考的问题。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>react native</category>
            <category>原生</category>
            <category>心得分享</category>
        </item>
        <item>
            <title><![CDATA[有了 Prisma 就别用 TypeORM 了]]></title>
            <link>https://kuizuo.me/blog/with-prisma-dont-use-typeorm</link>
            <guid>https://kuizuo.me/blog/with-prisma-dont-use-typeorm</guid>
            <pubDate>Sat, 13 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[要说 2024 年 Node.js 的 ORM 框架应该选择哪个？毫无疑问选 Prisma。至于为何，请听我细细道来。]]></description>
            <content:encoded><![CDATA[<p>要说 2024 年 Node.js 的 ORM 框架应该选择哪个？毫无疑问选 Prisma。至于为何，请听我细细道来。</p>
<p>本文面向的对象是饱受 TypeORM 折磨的资深用户(说的便是我自己)。只对这两个 ORM 框架从开发体验上进行对比，你也可以到 <a href="https://www.prisma.io/docs/orm/more/comparisons/prisma-and-typeorm" target="_blank" rel="noopener noreferrer">这里</a> 查看 Prisma 官方对这两个 ORM 框架的对比。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="整体对比">整体对比<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E6%95%B4%E4%BD%93%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="整体对比的直接链接" title="整体对比的直接链接">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="更新频率--下载量">更新频率 &amp; 下载量<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E6%9B%B4%E6%96%B0%E9%A2%91%E7%8E%87--%E4%B8%8B%E8%BD%BD%E9%87%8F" class="hash-link" aria-label="更新频率 &amp; 下载量的直接链接" title="更新频率 &amp; 下载量的直接链接">​</a></h3>
<p>TypeORM 距离上次更新已经过去半年之久了（下图截取自 24 年 1 月 1 日，没想到年初竟然还复活了）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165614-Untitled.png" alt="Untitled" class="img_OpE3"></p>
<p>从下载量以及 star 数来看，如今 Prisma 已经超过 TypeORM，这很大一部分的功劳归功于像 Next.js、Nuxt.js 这样的全栈框架。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165632-Untitled%201.png" alt="Untitled" class="img_OpE3"></p>
<p>上图来源 <a href="https://npmtrends.com/prisma-vs-typeorm" target="_blank" rel="noopener noreferrer">https://npmtrends.com/prisma-vs-typeorm</a></p>
<p>而在 Nest.js 的 <a href="https://discord.com/channels/520622812742811698/1156124199874732033" target="_blank" rel="noopener noreferrer">Discord 社区</a> 讨论之中，Prisma 也成为诸多 Nest.js 开发者首选的 ORM 框架，因为它有着更好的开发体验。</p>
<p>在大势所趋之下相信你内心已经有一份属于自己的答案。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="文档--生态">文档 &amp; 生态<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E6%96%87%E6%A1%A3--%E7%94%9F%E6%80%81" class="hash-link" aria-label="文档 &amp; 生态的直接链接" title="文档 &amp; 生态的直接链接">​</a></h3>
<p>从文档的细致程度上 Prisma 比 TypeORM 要清晰详尽。在 <a href="https://www.prisma.io/docs/getting-started" target="_blank" rel="noopener noreferrer">Get started</a> 花个数十分钟了解 Prisma 基本使用，到 <a href="https://playground.prisma.io/" target="_blank" rel="noopener noreferrer">playground.prisma.io</a> 中在线尝试，到 <a href="https://www.prisma.io/learn" target="_blank" rel="noopener noreferrer">learn</a> 查看官方所提供的免费教程。</p>
<p>此外 Prisma 不仅支持 js/ts 生态，还支持其他语言。丰富的<a href="https://www.prisma.io/ecosystem" target="_blank" rel="noopener noreferrer">生态</a>下，加之 Prisma 开发团队的背后是由商业公司维护，无需担心需求得不到解决。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165658-Untitled%202.png" alt="Untitled" class="img_OpE3"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="开发体验对比">开发体验对比<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E5%BC%80%E5%8F%91%E4%BD%93%E9%AA%8C%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="开发体验对比的直接链接" title="开发体验对比的直接链接">​</a></h2>
<p>在从开发体验上对比之前，我想先说说 TypeORM 都有哪些坑(不足)。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="findoneundefined-所查询到的却是第一条记录">findOne(undefined) 所查询到的却是第一条记录<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#findoneundefined-%E6%89%80%E6%9F%A5%E8%AF%A2%E5%88%B0%E7%9A%84%E5%8D%B4%E6%98%AF%E7%AC%AC%E4%B8%80%E6%9D%A1%E8%AE%B0%E5%BD%95" class="hash-link" aria-label="findOne(undefined) 所查询到的却是第一条记录的直接链接" title="findOne(undefined) 所查询到的却是第一条记录的直接链接">​</a></h3>
<p>首先 TypeORM 有个天坑，你可以在 这个 <a href="https://github.com/typeorm/typeorm/issues/2500" target="_blank" rel="noopener noreferrer">Issue</a> 中查看详情或查看 <a href="https://pietrzakadrian.com/blog/how-to-hack-your-nodejs-application-which-uses-typeorsm" target="_blank" rel="noopener noreferrer">这篇文章</a> 是如何破解使用 TypeORM 的 Node.js 应用。</p>
<p>当你使用 <code>userRepository.findOne({ where: { id: null } })</code> 时，从开发者的预期来看所返回的结果应该为 null 才对，但结果却是大跌眼镜，结果所返回的是 user 表中的第一个数据记录！</p>
<p>你可能会说，这不是 bug 吗？为何官方还不修。事实上确实是 bug，而事实上官方到目前也还没修复该 bug。再结合上文提到的更新频率，哦，那没事了。</p>
<p>目前解决方法则是用 <code>createQueryBuilder().where({ id }).getOne()</code> 平替上一条语句或者确保查询参数不为 undefined。从这也可以看的出，TypeORM 在现今或许并不是一个很好的选择。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="synchronize-true-导致数据丢失">synchronize: true 导致数据丢失<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#synchronize-true-%E5%AF%BC%E8%87%B4%E6%95%B0%E6%8D%AE%E4%B8%A2%E5%A4%B1" class="hash-link" aria-label="synchronize: true 导致数据丢失的直接链接" title="synchronize: true 导致数据丢失的直接链接">​</a></h3>
<p><code>synchronize</code> 表示数据库的结构是否和代码保持同步，官方提及到请不要在生产环境中使用，但在开发阶段这也并不是一个很好的做法。举个例子，有这么一个实体</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>user.entity.ts<span style="flex:1;text-align:right">ts</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Entity</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">User</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">PrimaryGeneratedColumn</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">number</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Column</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>当开启了 <code>synchronize: true</code>，并且将 <code>name</code> 更改为 <code>title</code> 时，一旦运行 nest 服务后就会发现原有 <code>name</code> 下的数据全都丢失了！如图所示</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165658-Untitled%203.png" alt="Untitled" class="img_OpE3"></p>
<p>因为 TypeORM 针对上述操作的 sql 语句是这样的</p>
<div class="language-sql codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-sql codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token identifier">user</span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token plain"> CHANGE </span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token identifier">name</span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token identifier">title</span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">varchar</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">255</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">NOT</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(35, 99%, 36%)">NULL</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token identifier">user</span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">DROP</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">COLUMN</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token identifier">title</span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token identifier">user</span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">ADD</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token identifier">title</span><span class="token identifier punctuation" style="color:hsl(119, 34%, 47%)">`</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">varchar</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">255</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">NOT</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(35, 99%, 36%)">NULL</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>也就是说，当你在开发环境中，修改某个字段（包括名字，属性）时，该字段原有的数据便会清空。</p>
<p>因此针对数据库更新的操作最正确的做法是使用迁移(migrate)。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="接入成本">接入成本<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E6%8E%A5%E5%85%A5%E6%88%90%E6%9C%AC" class="hash-link" aria-label="接入成本的直接链接" title="接入成本的直接链接">​</a></h3>
<p>在 Nest 项目中，Prisma 的接入成本远比 TypeORM 来的容易许多。</p>
<p>相信你一定有在 <code>xxx.module.ts</code> 中在 imports 中导入 <code>TypeOrmModule.forFeature([xxxEntity])</code> 的经历。就像下面代码这样：</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>xxx.module.ts<span style="flex:1;text-align:right">ts</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Module</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  imports</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">TypeOrmModule</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">forFeature</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">UserEntity</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  controllers</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">UserController</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  providers</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">UserService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  exports</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">TypeOrmModule</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> UserService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">UserModule</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>对于初学者而言，很大程度上会忘记导入 <code>xxxEntity</code>，就会出现这样的报错</p>
<div class="language-bash codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-bash codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">Potential solutions:</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"> - Is UserModule a valid NestJS module?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"> - If </span><span class="token string" style="color:hsl(119, 34%, 47%)">"UserEntityRepository"</span><span class="token plain"> is a provider, is it part of the current UserModule?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"> - If </span><span class="token string" style="color:hsl(119, 34%, 47%)">"UserEntityRepository"</span><span class="token plain"> is exported from a separate @Module, is that module imported within UserModule?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">   @Module</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">     imports: </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain"> /* the Module containing </span><span class="token string" style="color:hsl(119, 34%, 47%)">"UserEntityRepository"</span><span class="token plain"> */ </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">   </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">Error: Nest can't resolve dependencies of the userService </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">?</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain">. Please </span><span class="token function" style="color:hsl(221, 87%, 60%)">make</span><span class="token plain"> sure that the argument </span><span class="token string" style="color:hsl(119, 34%, 47%)">"UserEntityRepository"</span><span class="token plain"> at index </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> is available </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">in</span><span class="token plain"> the UserModule context.</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此外这还不是最繁琐的，你还需要再各个 service 中，通过下面的代码来注入 userRepository。</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>user.service.ts<span style="flex:1;text-align:right">ts</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">InjectRepository</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">UserEntity</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">private</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">readonly</span><span class="token plain"> userRepository</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Repository</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">UserEntity</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>一旦实体一多，要注入的 Repository 也就更多，无疑不是对开发者心智负担的加深。</p>
<p>再来看看 Prisma 是怎么导入的，你可以使用 <a href="https://nestjs-prisma.dev/docs/basic-usage/" target="_blank" rel="noopener noreferrer">nestjs-prisma</a> 或者按照官方文档中<a href="https://docs.nestjs.com/recipes/prisma#use-prisma-client-in-your-nestjs-services" target="_blank" rel="noopener noreferrer">创建 PrismaService</a>。</p>
<p>然后在 service 上，注入 PrismaService 后，就可以通过 <code>this.prisma[model]</code> 来调用模型(实体) ，就像这样</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>app.service.ts<span style="flex:1;text-align:right">ts</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> Injectable </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@nestjs/common'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> PrismaService </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'nestjs-prisma'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Injectable</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">AppService</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">constructor</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">private</span><span class="token plain"> prisma</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> PrismaService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">users</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">findMany</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">userId</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">findUnique</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      where</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> userId </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>哪怕创建其他新的实体，只需要重新生成 PrismaClient，都无需再导入额外服务，this.prisma 便能操作所有与数据库相关的 api。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="更好的类型安全">更好的类型安全<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E6%9B%B4%E5%A5%BD%E7%9A%84%E7%B1%BB%E5%9E%8B%E5%AE%89%E5%85%A8" class="hash-link" aria-label="更好的类型安全的直接链接" title="更好的类型安全的直接链接">​</a></h3>
<p>Prisma 的贡献者中有 <a href="https://github.com/millsp/ts-toolbelt" target="_blank" rel="noopener noreferrer">ts-toolbelt</a> 的作者，正因此 Prisma 的类型推导十分强大，能够自动生成几乎所有的类型。</p>
<p>而反观 TypeORM 虽说使用 Typescript 所编写，但它的类型推导真是一言难尽。我举几个例子：</p>
<p>在 TypeORM 中，你需要 select 选择某个实体的几个字段，你可以这么写</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165658-Untitled%204.png" alt="Untitled" class="img_OpE3"></p>
<p>你会发现 post 对象的类型提示依旧还是 postEntity，没有任何变化。但从开发者的体验角度而言，<strong>既然我选择查询 id 和 title 两个字段，那么你所返回的 post 类型应该也只有 id 与 title 才更符合预期</strong>而后续代码中由于允许 post 有 body 属性提示，那么 post.body 为 null 这样不必要的结果。</p>
<p>再来看看 Prisma，你就会发现 post 对象的类型提示信息才符合开发者的预期。像这样的细节在 Prisma 有非常多。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165658-Untitled%205.png" alt="Untitled" class="img_OpE3"></p>
<p>这还不是最关键的，TypeORM 通常需要使用 <code>createQueryBuilder</code> 方法来构造 sql 语句来满足开发者所要查询的预期。而当你使用了该方法，你就会发现你所编写的代码与 js 无疑，我贴几张图给大伙看看。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165658-Untitled%206.png" alt="Untitled" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165658-Untitled%207.png" alt="Untitled" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165658-Untitled%208.png" alt="Untitled" class="img_OpE3"></p>
<p>这无疑会诱发一些潜在 bug，我就多次因为要 select 某表中的某个字段，却因拼写错误导致查询失败。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="创建实体">创建实体<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E5%88%9B%E5%BB%BA%E5%AE%9E%E4%BD%93" class="hash-link" aria-label="创建实体的直接链接" title="创建实体的直接链接">​</a></h3>
<p>在 TypeORM 中，假设你要新增一条 User 记录，你通常需要这么做</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> newUser </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">User</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">newUser</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">name </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'kuizuo'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">newUser</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">email </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hi@kuizuo.me'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> user </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> userRepository</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">save</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">newUser</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>当然你可以对 User 实体中做点手脚，像下面这样加一个构造函数</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>user.entity.ts<span style="flex:1;text-align:right">ts</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Entity</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">User</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">PrimaryGeneratedColumn</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">number</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Column</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> unique</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(35, 99%, 36%)">true</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  username</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Column</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  email</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">constructor</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">partial</span><span class="token operator" style="color:hsl(221, 87%, 60%)">?</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Partial</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">UserEntity</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    Object</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">assign</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> partial</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> newUser </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">User</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'kuizuo'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  email</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hi@kuizuo.me'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> user </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> userRepository</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">save</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">newUser</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>于是你就可以传递一个 js 对象到 User 实体，而不是 newUser.xxx = xxx 像 Java 版的写法。</p>
<p>而要是涉及到多个关联的数据，往往需要先查询到关联数据，然后再像上面这样赋值+保存。这里就不展开了，使用过 TypeORM 的应该深有体会。</p>
<p>而在 Prisma 中，绝大多数的操作你都只需要一条代码语句外加一个对象结构，像上述 TypeORM 的操作对应 Prisma 的代码语句如下</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> user </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">create</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  data</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'kuizuo'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    email</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hi@kuizuo.me'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="根据条件来创建还是更新">根据条件来创建还是更新<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E6%A0%B9%E6%8D%AE%E6%9D%A1%E4%BB%B6%E6%9D%A5%E5%88%9B%E5%BB%BA%E8%BF%98%E6%98%AF%E6%9B%B4%E6%96%B0" class="hash-link" aria-label="根据条件来创建还是更新的直接链接" title="根据条件来创建还是更新的直接链接">​</a></h3>
<p>在数据库中操作经常需要判断数据库中是否有某条记录，以此来决定是更改该记录还是创建新的一条记录，而在 Prisma 中，完全可以使用 upsert，就像下面这样</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> user </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">upsert</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  where</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  update</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> email</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'example@prisma.io'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  create</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> email</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'example@prisma.io'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="聚合函数">聚合函数<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E8%81%9A%E5%90%88%E5%87%BD%E6%95%B0" class="hash-link" aria-label="聚合函数的直接链接" title="聚合函数的直接链接">​</a></h3>
<p>在 TypeORM 中，假设你需要使用聚合函数来查询的话，通常会这么写</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> raw </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">userRepository</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">createQueryBuilder</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'user'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">select</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'SUM(user.id)'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'sum'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">getRawOne</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> sum </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> raw</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">sum</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>如果只是像上面这样，单纯查询 sum，那么 raw 的值是 <code>{ sum: 1 }</code> , 但最要命的就是 <code>select</code> 配合 <code>getRawOne</code> 还要额外查询 user 实体的属性，所得到的结果就像这样</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> raw </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">userRepository</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">createQueryBuilder</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'user'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">select</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'SUM(user.id)'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'sum'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">addSelect</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'user'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">where</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'user.id = :id'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">getRawOne</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">	user_id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">	user_name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'kuizuo'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">	user_email： </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hi@kuizuo.me'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">	sum</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'1'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>所有 user 的属性都会带有 <code>user_</code> 前缀，这看上去有点不是那么合理，但如果考虑要联表查询的情况下，就会存在相同名称的字段，通过添加表名(别名)前缀就可以避免这种情况，这样来看貌似又有点合理了。</p>
<p>但还是回到熟悉的类型安全，这里的所返回的 raw 对象是个 any 类型，一样不会有任何提示。</p>
<p>而在 Prisma 中，提供了 专门用于聚合的方法 <a href="https://www.prisma.io/docs/orm/reference/prisma-client-reference#aggregate" target="_blank" rel="noopener noreferrer">aggregate</a>，可以特别轻松的实现聚合函数查询。</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> minMaxAge </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">aggregate</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  _count</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    _all</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(35, 99%, 36%)">true</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  _max</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    profileViews</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(35, 99%, 36%)">true</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  _min</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    profileViews</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(35, 99%, 36%)">true</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  _count</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> _all</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">29</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  _max</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> profileViews</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">90</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  _min</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> profileViews</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<hr>
<p>看到这里，你若是长期使用 TypeORM 的用户必定会感同身受如此糟糕的体验。那种开发体验真的是无法用言语来形容的。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="prisma-生态">Prisma 生态<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#prisma-%E7%94%9F%E6%80%81" class="hash-link" aria-label="Prisma 生态的直接链接" title="Prisma 生态的直接链接">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="分页">分页<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E5%88%86%E9%A1%B5" class="hash-link" aria-label="分页的直接链接" title="分页的直接链接">​</a></h3>
<p>在 Prisma 你要实现分页，只需要在 prismaClient 继承 <a href="https://github.com/deptyped/prisma-extension-pagination" target="_blank" rel="noopener noreferrer">prisma-extension-pagination</a> 这个库。就可像下面这样，便可在 model 中使用paginate方法来实现分页，如下代码。</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> PrismaClient </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@prisma/client'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> pagination </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'prisma-extension-pagination'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> prisma </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">PrismaClient</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">$</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">extends</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token function" style="color:hsl(221, 87%, 60%)">pagination</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">users</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> meta</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">user</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">paginate</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">withPages</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    limit</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">10</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    page</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">2</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    includePageCount</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(35, 99%, 36%)">true</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token comment" style="color:hsl(230, 4%, 64%)">// meta contains the following</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  currentPage</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">2</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  isFirstPage</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(35, 99%, 36%)">false</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  isLastPage</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(35, 99%, 36%)">false</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  previousPage</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  nextPage</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">3</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  pageCount</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">10</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// the number of pages is calculated</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  totalCount</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">100</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// the total number of results is calculated</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>支持页数(page)或光标(cursor)。</p>
<div class="theme-admonition theme-admonition-tip admonition_fh9h alert alert--success"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>两种分页的使用场景</div><div class="admonitionContent_oz3Y"><p>按页查询: 用于传统分页，例如翻页</p><p>光标查询: 根据游标进行查询，例如无限滚动</p></div></div>
<p>而在 TypeORM 你通常需要自己封装一个 paginate方法，就如下面代码所示（以下写法借用 <a href="https://www.npmjs.com/package/nestjs-typeorm-paginate" target="_blank" rel="noopener noreferrer">nestjs-typeorm-paginate</a>）</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token generic-function function" style="color:hsl(221, 87%, 60%)">paginate</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token generic-function generic class-name constant" style="color:hsl(35, 99%, 36%)">T</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  queryBuilder</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> SelectQueryBuilder</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token constant" style="color:hsl(35, 99%, 36%)">T</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  options</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> IPaginationOptions</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">Promise</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">Pagination</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token constant" style="color:hsl(35, 99%, 36%)">T</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> page</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> limit </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> options</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  queryBuilder</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">take</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">limit</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">skip</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">page </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">*</span><span class="token plain"> limit</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">items</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> total</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> queryBuilder</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">getManyAndCount</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token generic-function function" style="color:hsl(221, 87%, 60%)">createPaginationObject</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token generic-function generic class-name constant" style="color:hsl(35, 99%, 36%)">T</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    items</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    totalItems</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> total</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    currentPage</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> page</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    limit</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token comment" style="color:hsl(230, 4%, 64%)">// example</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> queryBuilder </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> userRepository</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">createQueryBuilder</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'user'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> items</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> meta </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">paginate</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">queryBuilder</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> page</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> limit </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>当然也可以自定义userRepository，为其添加 paginate 方法，支持链式调用。但这无疑增添了开发成本。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="根据-schema-自动生成数据验证">根据 Schema 自动生成数据验证<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E6%A0%B9%E6%8D%AE-schema-%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81" class="hash-link" aria-label="根据 Schema 自动生成数据验证的直接链接" title="根据 Schema 自动生成数据验证的直接链接">​</a></h3>
<p>得益于 Prisma 强大的数据建模 dsl，通过 <a href="https://www.prisma.io/docs/orm/prisma-schema/overview/generators" target="_blank" rel="noopener noreferrer">generators</a> 生成我们所需要的内容（文档，类型），比如可以使用 <a href="https://github.com/chrishoermann/zod-prisma-types" target="_blank" rel="noopener noreferrer">zod-prisma-types</a> 根据 Schema 生成 <a href="https://github.com/colinhacks/zod" target="_blank" rel="noopener noreferrer">zod</a> 验证器**。**</p>
<p>举个例子，可以为 schema.prisma 添加一条 generator，长下面这样</p>
<div class="language-prisma codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>prisma.schema<span style="flex:1;text-align:right">prisma</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-prisma codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">generator client {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  provider = "prisma-client-js"</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  output   = "./client"</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">generator zod {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  provider                         = "zod-prisma-types"</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  output                           = "./zod"</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  createModelTypes                 = true</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">	// ...rest of config</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">datasource db {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  provider = "postgresql"</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  url      = env("DATABASE_URL")</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">model User {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  id         String      @id @default(uuid())</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  email      String      @unique</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  name       String?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>执行构建命令后，这将会自动生成 zod/index.ts 文件，将包含 UserSchema 信息，其中片段代码如下</p>
<div class="language-ts codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>zod/index.ts<span style="flex:1;text-align:right">ts</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-ts codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> UserSchema </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">object</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">uuid</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  email</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">nullable</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">type</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">User</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">infer</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">typeof</span><span class="token plain"> UserSchema</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>再通过 createZodDto，将 zod 验证器转化为 dto 类，就像下面这样</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165658-Untitled%209.png" alt="Untitled" class="img_OpE3"></p>
<p>当然你可能并不想在 nestjs 项目中使用 zod，而是希望使用传统的 <a href="https://www.npmjs.com/package/class-validator" target="_blank" rel="noopener noreferrer">class-validator</a> 来编写 dto。可以使用社区提供的 <a href="https://github.com/kimjbstar/prisma-class-generator" target="_blank" rel="noopener noreferrer">prisma-class-generator</a> 根据已有 model 生成 dto。</p>
<hr>
<p>合理来说，Prisma 并不是一个传统的 ORM，它的工作原理并不是将表映射到编程语言中的模型类，为处理关系数据库提供了一种面向对象的方式。而是在 Prisma Schema 中定义模型。在应用程序代码中，您可以使用 Prisma Client 以类型安全的方式读取和写入数据库中的数据，而无需管理复杂模型实例的开销。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113171541-202401131715135.png" alt="" class="img_OpE3"></p>
<p>总而言之，你若想要<a href="https://www.prisma.io/docs/orm/prisma-client/type-safety" target="_blank" rel="noopener noreferrer">更好的类型</a>，简洁的<a href="https://www.prisma.io/docs/orm/prisma-schema/data-model/database-mapping#prismas-default-naming-conventions-for-indexes-and-constraints" target="_blank" rel="noopener noreferrer">实体声明语法</a>，况且带有<a href="https://www.prisma.io/studio" target="_blank" rel="noopener noreferrer">可视化桌面端应用</a>，以及更好的<a href="https://www.prisma.io/ecosystem" target="_blank" rel="noopener noreferrer">生态完备</a>，那么你就应该选 Prisma。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="总结">总结<a href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接">​</a></h2>
<p>在写这篇文章时，我也是彻底的将 Nestjs 项目中由 TypeORM 迁移到 Prisma ，这期间给我最大的变化就是在极少的代码量却又能实现强大的功能。许多涉及多表的 CRUD操作可以通过一条简洁的表达式来完成，而在使用 TypeORM 时，常常需要编写繁琐臃肿的 queryBuilder。</p>
<p>TypeORM 有种被 nestjs 深度绑定的模样，一提到 TypeORM，想必第一印象就是 Nestjs 中所用到的 ORM 框架。然而，Prisma 却不同，是一个全能通用的选择，可以在任何的 js/ts 框架中使用。</p>
<p>从开发体验的角度不接受任何选择 TypeORM 的反驳，有了更优优秀的选择，便不愿意也不可能在回去了。如果你还未尝试过 Prisma，我强烈建议你亲身体验一番。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>orm</category>
            <category>prisma</category>
            <category>typeorm</category>
        </item>
        <item>
            <title><![CDATA[2023 · 谈谈职业规划]]></title>
            <link>https://kuizuo.me/blog/2023-year-end-summary</link>
            <guid>https://kuizuo.me/blog/2023-year-end-summary</guid>
            <pubDate>Mon, 25 Dec 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[又到了年底写年终总结的时候了，说实话今年感觉没什么内容可写。上半年发生了比较多的事不方便叙述，而下半年我忙于学校课程 + 课程重修，过得其实还有点浑浑噩噩。]]></description>
            <content:encoded><![CDATA[<p>又到了年底写年终总结的时候了，说实话今年感觉没什么内容可写。上半年发生了比较多的事不方便叙述，而下半年我忙于学校课程 + 课程重修，过得其实还有点浑浑噩噩。</p>
<p>不过如今都大四了，也确实是要考虑实习的事了。我想结合我自身情况，谈谈我是怎么看待工作或者往远点说职业规划方面的想法。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="一些经历">一些经历<a href="https://kuizuo.me/blog/2023-year-end-summary#%E4%B8%80%E4%BA%9B%E7%BB%8F%E5%8E%86" class="hash-link" aria-label="一些经历的直接链接" title="一些经历的直接链接">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="工作经历">工作经历<a href="https://kuizuo.me/blog/2023-year-end-summary#%E5%B7%A5%E4%BD%9C%E7%BB%8F%E5%8E%86" class="hash-link" aria-label="工作经历的直接链接" title="工作经历的直接链接">​</a></h3>
<p>我目前一共有 3 段工作经历</p>
<ol>
<li>休学一年在厦门本地某工作室(共4人)与他人创业。 <span style="float:right"> 2021.1 ~ 2022.1</span></li>
<li>在北京的一家公司(规模不大)远程实习。 <span style="float:right">2022.8 ~ 2022.10</span></li>
<li>在 <a href="https://3rcd.com/" target="_blank" rel="noopener noreferrer">3R 教室</a> 兼任助教与开发组成员。 <span style="float:right"> 2023.1 ~ 2023.12</span></li>
</ol>
<p>事实上，对我而言也仅有休学的那一段才能算是工作，而其他两者从年限与工作性质来看不能算是一份真正的工作。但也不能说不是，至少也替别人做过事、签过合同、领过工资的。</p>
<p>不过我想稍微提提我是怎么找到这三个工作的，或者说这三份工作是怎么找到我的 -&gt; <strong>全靠分享</strong></p>
<p>我不止说过一次分享的重要性，我的第一份工作就是在网络分享了<a href="https://kuizuo.me/blog/chaoxing-helper">一个大学生自动完成视频、作业的程序</a>，恰好被我当地一个工作室的同事看到，寻求我能否改进功能一同合作，于是一拍即可，便开始<a href="https://kuizuo.me/blog/narrate-a-college-student#%E4%BC%91%E5%AD%A6">休学</a>。</p>
<p>剩余的两个工作同样也是，一个契机是我当时正好在研究 <a href="https://strapi.io/" target="_blank" rel="noopener noreferrer">Strapi</a> 编写了一篇文章，这家公司恰好用到这门技术；而 3R教室则是因为创始人所用的与我博客相同的网站生成器 <a href="https://docusaurus.io/zh-CN" target="_blank" rel="noopener noreferrer">Docusaurus</a> 在这个机缘下认识的，后来创始人创业开了3R教室，我也就利用业余时间在其中扮演助教身份，赚个零花钱。（其实我很热心肠的，哪怕我不当助教，只要时间允许的情况下，你有问题问我也会尽数回答）</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="接单经历">接单经历<a href="https://kuizuo.me/blog/2023-year-end-summary#%E6%8E%A5%E5%8D%95%E7%BB%8F%E5%8E%86" class="hash-link" aria-label="接单经历的直接链接" title="接单经历的直接链接">​</a></h3>
<p>同样的，分享也能很大程度上提升你接单的资本。</p>
<p>我并非想要炫耀什么，而是分享实实在在给我带来了很多好处，我希望你能够分享一些内容，一些见解心得，哪怕是一些笔记、对他人问题的回复，都可能给你带来一些意想不到的好处。</p>
<p>我就有一个同学将自己的笔记、课设以及每次专业课期末考试的文档放到 CSDN 上（没错就是那个 IT 界的毒瘤），现在这不到期末了嘛，他的私信就有一堆人找他写课设啥的。</p>
<p>回归到正题，我接过单不多也不少，绝大多数是那种对我而言挺简单的问题，但对于一些人而言就比较困难。例如前几天一个例子，我编写过 <a href="https://github.com/kuizuo/js-deobfuscator" target="_blank" rel="noopener noreferrer">js-deobfuscator</a> 一个 js 混淆还原的工具，正好有人需要将一份混淆代码还原出来，于是寻求我的帮助，发了个红包给我。有时哪怕只是回答别人一个问题，甚至都可能会收到来自他人的红包。想想看，你在技术群里是不是有过这个现象。</p>
<p>还有一个网站的单子我印象很深，是一个用于销售流量、虚拟会员等商品的网站，不同与普通的商城系统，充值是通过卡密，使用卡密到特定的网站上使用。当时一个网站全套 5000，客户也算熟人了，还帮我推荐几个人购买，相当于这一套网站(模版)，帮我赚了几 w，还省去我很多开发成本，后续我都无偿给他提供技术服务。</p>
<p>事实上至少现在为止我并不是很喜欢接单，也很少主动接单。小的单（也可叫零活）通常以一顿夜宵作为回报或是看在人情的份上；而大的单（通常为外包单），通常要求交付时间快，很多时候只为了更快的完成功能，而不考虑代码质量。并且通常没有维护性可言，就更别说写测试和重构了。写久了，代码虽然写的是快了，但堆屎山的速度都堪比屎壳郎了。代码能力的提升反而不是很大。</p>
<p>在我看来当因某种目的收了他的钱财时，往往就要花费时间精力去完成这个目的，而从我内心上很难平衡这点。所以只要我不收钱，我就可以不做事了（bushi</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="赚钱经历">赚钱经历<a href="https://kuizuo.me/blog/2023-year-end-summary#%E8%B5%9A%E9%92%B1%E7%BB%8F%E5%8E%86" class="hash-link" aria-label="赚钱经历的直接链接" title="赚钱经历的直接链接">​</a></h3>
<p>我赚的第一桶金其实还不是我工作，在上大学前我已经赚过对我两桶金了，还全都是依靠网络上的资源，而非现实打工。一次是初三当时向别人学习如何刷钻以及购买钻卡来接单帮别人刷钻，另一个是作为线报群群主（现如今更多的称之为羊毛群，但我更愿称撸界），发布一些活动和教程通过拉人头引流赚钱。而也是再次契机之下，学会了开发(定制)软件。</p>
<p>这并非本文重点，因此我并不想过多介绍，但或许这会作为后面的铺垫。</p>
<p>不过我还是想说：<strong>赚钱不易</strong>。我现在回看我过去的一些经历，只能说运气成分很大，而又恰好在风口之中。在如今的互联网环境下，想要复刻曾经的路, 无疑是死路一条。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="为何不实习">为何不实习？<a href="https://kuizuo.me/blog/2023-year-end-summary#%E4%B8%BA%E4%BD%95%E4%B8%8D%E5%AE%9E%E4%B9%A0" class="hash-link" aria-label="为何不实习？的直接链接" title="为何不实习？的直接链接">​</a></h2>
<p>背景介绍完了，那就来说说实习的事情，为何我都大四了，还不找实习？</p>
<p>不是不找，而是很难找到满意的。我在今年暑期的时候有尝试找过，投过几家大厂，无一例外，了无音讯。这对我当时而言，打击还蛮大的。因为三流学校出身，确实很难过（双关），加之没人内推，海投基本上是没希望的。</p>
<p>如今前端的职位卷之又卷，僧多粥少，在厦门一些中小公司所提供的前端实习待遇（薪资大约在 3k~4k），况且对自身的提升并不是特别大，在我不是一笔性价比划得来的买卖。</p>
<p>反观学校的情况那可就更头疼了，你敢相信在大四上最该实习的时候，极为不合理的教案却还要给学生安排课程，还是专业课的那种，以下我的课表。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2023/1231064437.png" alt="" class="img_OpE3"></p>
<p>此外，我还需要重修当初因休学而没去考试的几门课程（还挺多的，重修费还交了我不少😢），导致我最后一场考试时间是在 12月中旬。还是强调那一点，我不希望工作与学业这两者同时进行。关于这学校的诸多不满，待我毕业后我一定要说说，感受什么叫中国的私立大学。</p>
<p>此外还有一点，也是我不想提及的一点，这段期间我是处于“监视”状态，内心总悬着一个不知何时爆炸的炸弹，生怕突然爆炸将会打乱我的行程。</p>
<p>好在如今期限已到，心里悬着的一块石头终于放下来了，如释重负。</p>
<p>于是在上述的因素下，今年下半年我就不想找实习了。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="为何又想实习了">为何又想实习了？<a href="https://kuizuo.me/blog/2023-year-end-summary#%E4%B8%BA%E4%BD%95%E5%8F%88%E6%83%B3%E5%AE%9E%E4%B9%A0%E4%BA%86" class="hash-link" aria-label="为何又想实习了？的直接链接" title="为何又想实习了？的直接链接">​</a></h2>
<p>最主要的一点就是以我目前学生(应届生)的身份，是可以有机会争取到一份好的实习的，乃至是大厂的实习。</p>
<p>其次是我并没有一个很好的项目演进经历，看完我的一些工作经历不难发现我所待的公司/工作室的体量都不大，甚至我到现在都没真正体验过打卡上班（当然我也希望不要有）。而这就是小厂或者初创公司的同病，各个流程所要负责的任务很模糊。我当时休学和他人创业工作，基本上都是我一人负责项目开发；第二个远程实习也是我当时主动退出的，因为工作形式与接单无疑；第三个就不用多说了。</p>
<p>尤其是在技术层面的团队协作之中，还缺乏相当多的实践经验。所以在自我分析下，为了学习某些只在公司才能学到的东西，就非常有必要到大公司去一趟。</p>
<p>我大概率以后是不太可能再考虑坐班，所以这或许是我仅有为数不多的上班经历了。不过我想既便真正要开始打卡上班，要开始适应国内 996，未来我铁定会后悔有过这段历程。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202312250547469.png" alt="1703021156782.png" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="那么该怎么找呢">那么该怎么找呢？<a href="https://kuizuo.me/blog/2023-year-end-summary#%E9%82%A3%E4%B9%88%E8%AF%A5%E6%80%8E%E4%B9%88%E6%89%BE%E5%91%A2" class="hash-link" aria-label="那么该怎么找呢？的直接链接" title="那么该怎么找呢？的直接链接">​</a></h3>
<p>那么在如今就业形势如此严峻的时代，我又该如何找工作？</p>
<p>关于这点我其实并没有什么很好的经验分享，这也算是我首次主动找工作。不过我可以肯定的一点是，只单靠海投与某些招聘软件，想要拿到一份心意的 offer 很难。若是能通过一些关系，牵一条线（走内推），才是最佳选择，所以多积攒人脉交际圈是很有必要的。</p>
<p>这一部分我想待我后续找到工作后，再来做个心得分享也不为迟。（不说了，我去准备项目与简历去了）</p>
<div class="theme-admonition theme-admonition-success admonition_fh9h alert alert--success"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>补</div><div class="admonitionContent_oz3Y"><p><a href="https://kuizuo.me/blog/experience-of-an-ai-company">记 · 在 AI 公司入职一个月的体验与感悟</a></p></div></div>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="远程工作">远程工作<a href="https://kuizuo.me/blog/2023-year-end-summary#%E8%BF%9C%E7%A8%8B%E5%B7%A5%E4%BD%9C" class="hash-link" aria-label="远程工作的直接链接" title="远程工作的直接链接">​</a></h2>
<p><a href="https://eleduck.com/" target="_blank" rel="noopener noreferrer">电鸭</a>的sologen：<strong>只工作，不上班</strong>。很好表明远程工作的意图。</p>
<p>远程工作（remote work）是我当下认为最具性价比的工作形式，你可以过着四五线城市的生活水平而不用考虑一线城市的房租与消费，却赚取一线城市的薪资。往大点说就是挣美元花人民币。</p>
<p>但是远程工作并非多数人想的那么轻松的，很多人对远程工作有个误区，就是可以自由决定上班时间，很自由。其实不是的，不用上下班通勤，那就把通勤的时间拿来工作。没有额外房补开销，那就拿来压低工资。而该开的会还是得开，说白了就是换个地方上班，懒床是睡不了一点的，好一点的是冬天起来不用遭受寒风的洗礼罢了。</p>
<p>有的远程工作还会要求你记录每个时间段(细到每小时)你都做了哪些工作，我暑期的就有一份我老师推荐的远程工作(实习)就是这样，我就没干了。要论自由，接单/自由职业是最自由的，项目进度、时间都由你自己把握。</p>
<p>此外还有一点就是技术栈的因素。我并没有跟随国内的主流的 Web 开发技术栈去学习什么 Java，也庆幸还好当初认为 Java 这门语言繁琐的要死，让我转变使用 JS/TS 来进行 Web 全栈的开发，而如今的重心也是在 JS/TS。</p>
<p>而这套技术栈在国外的远程工作的岗位占比很大，一些初创公司也会采用这套技术栈。而我本身学的就是 JS/TS，自身优势反而更能体现出来。但反观在国内绝大多数公司我也就是老老实实做前端的那种。况且如果只想靠国内的主流传统的技术栈而去争取一个远程岗位，我认为当下还是很难实现的。就国内的职场环境下，如今工作都难找了，就别说本身岗位就少的远程工作了。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="正常坐班">正常坐班<a href="https://kuizuo.me/blog/2023-year-end-summary#%E6%AD%A3%E5%B8%B8%E5%9D%90%E7%8F%AD" class="hash-link" aria-label="正常坐班的直接链接" title="正常坐班的直接链接">​</a></h2>
<p>退而其次，那就正常上下班通勤，而这又有的抉择了。就以我自身举例把，我老家福建宁德且我在厦门某三流大学读书，意味着我只要在福建本地找工作，就可以过的相对来说舒服，人脉资源、衣食住行都不用过多考虑。但倘若选择在外打拼，意味着可能要适应陌生的城市生活，建立起新的人际网络，面对未知的困难和挑战。而这其中所能利用的、所要遭受的，都得由自个儿来承担。</p>
<p>今年冬至的时候我与家里人商讨过这个问题，家里人的意见更多偏向于留在本省，只求我稳稳当当，脚踏实地。因为我从小到大独立性很差，加上我确实有那么亿点宅，到外面恐怕是吃不消。所以在他们眼里哪怕在本省薪资不是令我那么满意，但至少可以过一种相对舒适的生活。而我自己则认为有必要去外面见见世面，有时候很多苦只有自己吃过才知道有苦。</p>
<p>不过最终如何选择，还得取决于我自己。这对于多数人也是值得花时间思考的。</p>
<p>最终如果选择重归故里，那么一开始就身在其中是否会更好？又何必在外漂泊，独自承受着一切。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="我对打工上班的看法">我对打工/上班的看法<a href="https://kuizuo.me/blog/2023-year-end-summary#%E6%88%91%E5%AF%B9%E6%89%93%E5%B7%A5%E4%B8%8A%E7%8F%AD%E7%9A%84%E7%9C%8B%E6%B3%95" class="hash-link" aria-label="我对打工/上班的看法的直接链接" title="我对打工/上班的看法的直接链接">​</a></h3>
<p>我经常拿我高考结束的那个暑假来做例子，我的一些高中同学去当外卖骑手、餐厅服务员，将他们的假期时间拿来打工赚钱，而我却埋头苦干的<a href="https://kuizuo.me/blog/2020-year-end-summary">学习编程(易语言)</a>，以兴趣驱使我学习一门技术。</p>
<p>或许是因为有过几段<a href="https://kuizuo.me/blog/2023-year-end-summary#%E8%B5%9A%E9%92%B1%E7%BB%8F%E5%8E%86">赚钱经历</a>，加上家境和个人意愿，所以至今为止我都不可能会去从事这种回报率不高，或者说没有“未来”的工作。与其打工/上班，尤其是耗费大量时间与体力劳动的，不如将时间拿来提升自己综合能力。（个人观点，或许有些极端）</p>
<p>可能是由于受自由的影响加上年轻有精力折腾，同时自己又是那么不走”寻常路“、不遵循“规则”的人。所以像那种安稳平淡，重复劳动的工作，如体制/编制内的职业就不太那么感冒。</p>
<p>但从现实是没人想打工/上班，却依旧有人从事这些工作。绝大多数打工人别无选择，因生活所迫，只得忍受和抱怨打工/上班的痛苦，却又寄人篱下。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="期望的工作">期望的工作<a href="https://kuizuo.me/blog/2023-year-end-summary#%E6%9C%9F%E6%9C%9B%E7%9A%84%E5%B7%A5%E4%BD%9C" class="hash-link" aria-label="期望的工作的直接链接" title="期望的工作的直接链接">​</a></h2>
<p>要说不工作是不可能的（但打工是不可能的），或许是因为这几年的一些工作经历有关，导致我有点习惯了远程，加上网间传闻 996 的压力下，我现在对坐班甚至还有些厌恶。对我最理想的工作环境就是能够自我在决定在某个时间点是否工作，例如在工作日放假，在节假日上班。</p>
<p>接单虽说也是，那这毕竟不是一份正经的工作，更多意义上来说是份兼职。更多是作为一时之需，接单又不可能接一辈子（搞得工作能工作一辈子似得）。</p>
<p>而我想说的是做个自由职业者，在细一点也就是独立开发者。但是现在来看这并非易事，不仅需要一定的技术能力和营销水平，最关键的想法就已经让绝大多数人对这份职业扼杀在摇篮中。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="现状">现状<a href="https://kuizuo.me/blog/2023-year-end-summary#%E7%8E%B0%E7%8A%B6" class="hash-link" aria-label="现状的直接链接" title="现状的直接链接">​</a></h2>
<p>首先我对自己的生活方式定义是：<strong>偏向于自发的能量爆发，而不是有条理的持续努力。</strong></p>
<p>今年我的 github contributions 出现过了几次长时间的空缺，那段期间我基本上没有在写代码，而是将心思用在其他事情上。例如只打游戏（好在游戏如今也是戒了）、只想摆烂（好在是摆的有点多了）、只想复习（好在该考的试都已经考了）。<strong>这就导致很多时候我会将全部精力专注在某件事情上，而抛弃其他与之无关的事情</strong>。</p>
<p>这也就导致了我很难养成一个良好的习惯，我的生物钟也因此而发生巨变。贴一张极为离谱的作息</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202312250547470.png" alt="Untitled" class="img_OpE3"></p>
<p>总之持之以恒在我这还不存在过，<strong>多数的开始是因一腔热血，而最后结束时却草草了事</strong>。</p>
<p>我想是是过度的自由造就了这般现状，想要解决这个问题，坐班可能还真可以。</p>
<hr>
<p>或许是因为比同龄人提前踏入过社会，有过几段工作经验，所以在就业形势严峻的时代，其实我反倒不是那么慌张。</p>
<p>何况目前经济状态也还算正常，哪怕不找工作也足够养活我自己好一整子。至少相比同龄人下，我已经挺富裕了。也没必要搞个打赏在自己的网站或项目上放置收款二维码等无关项目信息。对我而言，编写这些的目的不是为了接某个单，赚个打赏费，更多是一时兴起，分享给有需要的人，希望让其更为纯粹一些，仅此而已。</p>
<p>我对赚钱的态度也很简单，悦己便可（做点让自己愉悦的事情），数额能够维持自身生存便足够了。不过现在我还没开始到还贷的境地，或许只有压迫感来临时，才会让我激起对金钱的渴望，改变对金钱的态度。</p>
<p><strong>取悦自己，生活最好的心态。</strong></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="感慨">感慨<a href="https://kuizuo.me/blog/2023-year-end-summary#%E6%84%9F%E6%85%A8" class="hash-link" aria-label="感慨的直接链接" title="感慨的直接链接">​</a></h2>
<p>接触 web 开发算下整整两年半的时间，在这期间我并没有很好地扩张自己的技术面。在折腾方面确实不如以往，学习主动性也欠缺许多。不同于一开始所学习那样，喜欢瞎捣鼓，看到某个东西就会想尝试安装。</p>
<p>明年也就 24 岁了，面对即将到来的本命年，下一个阶段的走向其实很迷茫，面对我的是留学还是工作，我到现在都没有定数…</p>
<p>虽说我现在大四还在读，但其实我已经比同龄人晚毕业 1-2 年了（此时怀着感慨的泪水不禁流下）。心智上还保留着校园少年的青雉 ，同时也多了份成熟的稳重。</p>
<p>有过这些经历让我收获了更为珍贵的经验和独特的成长路径，我想未来的日子必定是丰富多彩的。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="往年回顾">往年回顾<a href="https://kuizuo.me/blog/2023-year-end-summary#%E5%BE%80%E5%B9%B4%E5%9B%9E%E9%A1%BE" class="hash-link" aria-label="往年回顾的直接链接" title="往年回顾的直接链接">​</a></h2>
<ul>
<li><a href="https://kuizuo.me/blog/2022-year-end-summary">2022 · 逆向到Web开发</a></li>
<li><a href="https://kuizuo.me/blog/2021-year-end-summary">2021 · 休学一年</a></li>
<li><a href="https://kuizuo.me/blog/2020-year-end-summary">2020 · 编程之旅-起点</a></li>
</ul>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>年终总结</category>
            <category>工作</category>
        </item>
        <item>
            <title><![CDATA[关于 restful api 路径定义的思考]]></title>
            <link>https://kuizuo.me/blog/restful-api-url-definition</link>
            <guid>https://kuizuo.me/blog/restful-api-url-definition</guid>
            <pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[关于 restful api 想必不用多说，已经有很多文章都阐述过它的设计原则，但遵循这个原则可以让你的 API 接口更加规范吗？以下是我对 restful api 风格的一些思考🤔。]]></description>
            <content:encoded><![CDATA[<p>关于 restful api 想必不用多说，已经有很多文章都阐述过它的设计原则，但遵循这个原则可以让你的 API 接口更加规范吗？以下是我对 restful api 风格的一些思考🤔。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="思考">思考<a href="https://kuizuo.me/blog/restful-api-url-definition#%E6%80%9D%E8%80%83" class="hash-link" aria-label="思考的直接链接" title="思考的直接链接">​</a></h2>
<p>此时不妨思考一个问题，现在以下几个接口，你会怎么去设计 url 路径？</p>
<ul>
<li>查询文章</li>
<li>查看文章详情</li>
<li>创建文章</li>
<li>更新文章</li>
<li>删除文章</li>
<li>查看我的文章</li>
<li>查看他人的文章</li>
</ul>
<p>前 5 个接口想必不难设计，这边就给出标准答案。</p>
<ul>
<li>查询文章 <code>GET /articles</code></li>
<li>查看某篇文章详情 <code>GET /articles/:id</code></li>
<li>创建文章 <code>POST /articles/</code></li>
<li>更新文章 <code>PUT /articles/:id</code></li>
<li>删除文章 <code>DELETE /articles/:id</code></li>
</ul>
<p>当然，我相信肯定也有<code>GET /article—list</code> <code>POST /add-article</code> 这样的答案，不过这些不在 restful api 风格的范畴，就不考虑了。</p>
<p>而这时 查看我的文章 或许就需要稍加思考，或许你会有以下几种方式</p>
<ul>
<li><code>GET /my-articles</code> 从资源角度来看肯定不好，因为此时在 url 不能很直观地体现请求资源，同时在控制器文件(controller) 就与 article 分离了，并且还占用了 / 下路径。</li>
<li><code>GET /articles/mine</code> 则又不那么遵循 restful api 风格，挺违和的。</li>
</ul>
<p>那么这时不妨遵循 <strong>资源从属关系</strong>，在这里 文章所属的对象就用户，因此查看他人的文章可以这么设计<code>GET /users/:userId/articles</code> 获取特定用户（userId）的文章列表。</p>
<p>而 查看我的文章 同样也可用此 URL，只需将 userId 更改为自己的便可。从 api 的 URL 来看是很舒服了，但是从代码开发的角度上问题又有了问题了。。。</p>
<p>对于 user 资源，是不是也有查询，创建，更新，删除等接口，即 查询用户 <code>GET /users</code>，创建用户<code>POST /users/</code> 等等。。</p>
<p>我是不是就需要在 user 这么重要的资源控制器上去添加一些其他方法，所对应的代码就如下所示</p>
<div class="language-jsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-jsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">@</span><span class="token function maybe-class-name" style="color:hsl(221, 87%, 60%)">Controller</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'users'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword module" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">UserController</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">constructor</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token parameter keyword" style="color:hsl(301, 63%, 40%)">private</span><span class="token parameter"> </span><span class="token parameter literal-property property" style="color:hsl(5, 74%, 59%)">userService</span><span class="token parameter operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token parameter"> </span><span class="token parameter maybe-class-name">UserService</span><span class="token parameter punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token parameter"> </span><span class="token parameter keyword" style="color:hsl(301, 63%, 40%)">private</span><span class="token parameter"> </span><span class="token parameter literal-property property" style="color:hsl(5, 74%, 59%)">articleService</span><span class="token parameter operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token parameter"> </span><span class="token parameter maybe-class-name">ArticleService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  @</span><span class="token function maybe-class-name" style="color:hsl(221, 87%, 60%)">Get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">list</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token parameter">@</span><span class="token parameter function maybe-class-name" style="color:hsl(221, 87%, 60%)">Query</span><span class="token parameter punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token parameter punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token parameter"> dto</span><span class="token parameter operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token parameter"> </span><span class="token parameter maybe-class-name">UserQueryDto</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword control-flow" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">userService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">findAll</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">dto</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  @</span><span class="token function maybe-class-name" style="color:hsl(221, 87%, 60%)">Get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">':id'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">info</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">@</span><span class="token function maybe-class-name" style="color:hsl(221, 87%, 60%)">Param</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'id'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> number</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword control-flow" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">userService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">findOne</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  @</span><span class="token function maybe-class-name" style="color:hsl(221, 87%, 60%)">Post</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">create</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token parameter">@</span><span class="token parameter function maybe-class-name" style="color:hsl(221, 87%, 60%)">Body</span><span class="token parameter punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token parameter punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token parameter"> dto</span><span class="token parameter operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token parameter"> </span><span class="token parameter maybe-class-name">UserCreateDto</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword control-flow" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">userService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">create</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">dto</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// 省略有关 User 部分接口，以下是其他 user 下的资源接口</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  @</span><span class="token function maybe-class-name" style="color:hsl(221, 87%, 60%)">Get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">':userId/articles'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">articles</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">@</span><span class="token function maybe-class-name" style="color:hsl(221, 87%, 60%)">Param</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'userId'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> userId</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> number</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword control-flow" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">userService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">findAll</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">userId</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> articlesId</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  @</span><span class="token function maybe-class-name" style="color:hsl(221, 87%, 60%)">Get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">':userId/articles/:articlesId'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">articles</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">@</span><span class="token function maybe-class-name" style="color:hsl(221, 87%, 60%)">Param</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'userId'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> userId</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> number</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> @</span><span class="token function maybe-class-name" style="color:hsl(221, 87%, 60%)">Param</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'articlesId'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> articlesId</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> number</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword control-flow" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">articleService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">find</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">userId</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> articlesId</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>换做是我，肯定不会希望将用户的代码与文章的代码混杂在一起。解决办法也是有的，可以额外创建一个新的 UserController 文件，专门用于获取用户下的资源（这里指 article），这样可以 即与原有针对 user 资源进行解耦，有可以有比较清晰接口分类。</p>
<div class="theme-admonition theme-admonition-warning admonition_fh9h alert alert--warning"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_oz3Y"><p>不过针对这种情况我可能的解决办法是下会额外 <strong>起一个别名</strong>，例如 author，将 <code>/users/:id/articles</code>转为 <code>/authors/:id/articles</code>，不过在这里指向的是用户 id，而不是新建一个 author 实体（资源）。</p><p>这里的 id 会根据情况而定，假设业务中需要创建 author 实体的情况下，对 author（作者）这一身份有一些操作，如普通用户变成一个作者，获取所有作者，那么这么做就再适合不过了。</p><p>在比如说一个更鲜明的例子 商店(store) 与 商品(product)。</p></div></div>
<p>业务再稍微复杂一下，现在要为业务增加以下几个功能，你又会如何设计</p>
<ul>
<li>收藏他人文章</li>
<li>获取我收藏的文章</li>
</ul>
<p>答案应该会有两种，即 <code>POST /articles/:articleId/collections</code> 与 <code>POST /collections</code></p>
<p>而这就令我特别头疼，因为这两个都符合 restful api 风格，也确实都能很好的满足业务功能。于是在我尝试抓包拥有相关的网站后，我发现几乎都是后者的 url。后来一想，前者更像是获取某种资源，而不是用于创建资源。后者确实更能胜任多数场景，比如说现在我需要收藏某个专栏，那么我用 <code>POST /collections</code> 足以胜任，只需要传递 条目id与条目类型，后端根据这两个条件找到对应条目数据便可。假设后续业务多一个资源需要收藏也不成问题。但换做前者的话，就得再多写一个重复性接口。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="抽象资源">抽象资源<a href="https://kuizuo.me/blog/restful-api-url-definition#%E6%8A%BD%E8%B1%A1%E8%B5%84%E6%BA%90" class="hash-link" aria-label="抽象资源的直接链接" title="抽象资源的直接链接">​</a></h2>
<p>restful 更多是针对实际存储的资源，核心是名词，对于增删改查的业务可以说非常适合，但现实情况下不只有增删改查，就例如上述的收藏功能。</p>
<p>对于一些个别接口需要另外表达，如 登录 <code>POST /login</code>、获取个人信息 <code>GET /profile</code></p>
<p>对于一些非增删改查的操作，还是使用 RPC 式的 API 更为实在，即 <strong><code>POST /命名空间/资源类型/动作</code></strong>，至少不用再为某个操作决定 PATCH/PUT 还是 POST/DELETE。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="针对同一实体区分不同用户">针对同一实体，区分不同用户<a href="https://kuizuo.me/blog/restful-api-url-definition#%E9%92%88%E5%AF%B9%E5%90%8C%E4%B8%80%E5%AE%9E%E4%BD%93%E5%8C%BA%E5%88%86%E4%B8%8D%E5%90%8C%E7%94%A8%E6%88%B7" class="hash-link" aria-label="针对同一实体，区分不同用户的直接链接" title="针对同一实体，区分不同用户的直接链接">​</a></h2>
<p>问题还没结束，不妨碍继续使用上述文章的例子，针对 文章 这一实体，又要怎么定义（区分）用户与作者或管理员路径呢？</p>
<p>管理员所看到的数据肯定远比用户来的多，如果使用同一个接口（如 <code>/articles</code>），那么业务代码必然会十分复杂。</p>
<p>使用不同的端点(end point) 是个解决方法，例如管理员在请求前添加 manage 或 admin，如 <code>/manage/articles</code> 或 <code>/articles/manage</code> 这样只需要多一步判断请求用户是否拥有管理的权限。</p>
<p>但对我个人而言，我一般都会以在一个命名空间下（这里指 <code>/articles</code>）编写，像前面的 <code>/manage/articles</code> 我是直接 pass 的。</p>
<p>在设计接口的原则就优先以拥有者的身份来设计，在去设计其他用户获取这个资源的接口。就比如说上述 <code>article</code> 为例， 针对增删改查而言，都是用于这个资源的拥有者可操作的，那么所获取到的数据就是尽可能符合拥有者需求的。而这时如果要将资源给其他角色请求，就会根据情况设计，如</p>
<ul>
<li><code>GET /articles</code> 获取我的文章列表（针对拥有者）</li>
<li><code>GET /articles/query</code> 查询文章（针对所有用户）</li>
</ul>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="权限区分">权限区分<a href="https://kuizuo.me/blog/restful-api-url-definition#%E6%9D%83%E9%99%90%E5%8C%BA%E5%88%86" class="hash-link" aria-label="权限区分的直接链接" title="权限区分的直接链接">​</a></h2>
<p>在 restful 中有两个概念：resources 与 action，因此只需要定义好权限标识码便可，还是以文章举例，如 <code>article:read</code> <code>article:create</code> <code>article:update</code> <code>article:delete</code> ，这里的 resources 对应的就是 article ，action 则是 read，create 等。将这些权限码分配给不同的控制器方法，在某个请求的时候判断用户是否拥有这个权限码便可。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="资源粒度问题">资源粒度问题<a href="https://kuizuo.me/blog/restful-api-url-definition#%E8%B5%84%E6%BA%90%E7%B2%92%E5%BA%A6%E9%97%AE%E9%A2%98" class="hash-link" aria-label="资源粒度问题的直接链接" title="资源粒度问题的直接链接">​</a></h2>
<p>但是复杂的实际业务中，仅仅单靠 restful API，往往需要发送多条请求，例如获取某篇文章数据与作者数据</p>
<div class="language-javascript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-javascript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token constant" style="color:hsl(35, 99%, 36%)">GET</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">/</span><span class="token plain">articles</span><span class="token operator" style="color:hsl(221, 87%, 60%)">/</span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token constant" style="color:hsl(35, 99%, 36%)">GET</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">/</span><span class="token plain">articles</span><span class="token operator" style="color:hsl(221, 87%, 60%)">/</span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token operator" style="color:hsl(221, 87%, 60%)">/</span><span class="token plain">author</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>要么两条请求获取相应数据，要么为调用方“定制”一个接口，如<code>GET /getArticleInfo</code>，这样只需一条请求便可得到想要的数据。但这个就破坏了 restful API 接口风格，并且在复杂的业务中，比如说还要获取博文的评论等等，后端就要额外提供一个接口，可以说是非常繁琐了。相比之下 <a href="https://graphql.org/" target="_blank" rel="noopener noreferrer">GraphQL</a> 就更为灵活了。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="写到最后">写到最后<a href="https://kuizuo.me/blog/restful-api-url-definition#%E5%86%99%E5%88%B0%E6%9C%80%E5%90%8E" class="hash-link" aria-label="写到最后的直接链接" title="写到最后的直接链接">​</a></h2>
<p>在我写这篇文章之前，我尝试抓包看过很多网站的请求 url，见识到各式各样的 url 路径，基本上很难找到遵循 restful api 风格的网站，绝大多数的操作除了获取外用 GET，其余全用 POST 。对于复杂的业务，restful api 风格实在过于难以胜任。</p>
<p>如果说变量命名是编程最大的痛苦，那么写接口最大的痛苦我想就是定义 url 路径了。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="相关文章">相关文章<a href="https://kuizuo.me/blog/restful-api-url-definition#%E7%9B%B8%E5%85%B3%E6%96%87%E7%AB%A0" class="hash-link" aria-label="相关文章的直接链接" title="相关文章的直接链接">​</a></h2>
<p><a href="https://www.v2ex.com/t/482682" target="_blank" rel="noopener noreferrer">RESTful API 对于同一实体，如何定义管理员和用户的路径？</a></p>
<p><a href="https://blog.51cto.com/LiatscBookshelf/5427906" target="_blank" rel="noopener noreferrer">RESTful API设计经验总结</a></p>
<p><a href="https://www.zhihu.com/question/438825740" target="_blank" rel="noopener noreferrer">为什么很多后端写接口都不按照 restful 规范？</a></p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>杂谈</category>
            <category>restful</category>
        </item>
        <item>
            <title><![CDATA[一位未曾涉足算法的初学者收获]]></title>
            <link>https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte</link>
            <guid>https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte</guid>
            <pubDate>Sat, 16 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[正如标题所言，在我四年的编程经历中就没刷过一道算法题，这可能与我所编写的应用有关，算法对我而言提升不是特别大。加上我几乎都是在需求中学习，而非系统性的学习。所以像算法这种基础知识我自然就不是很熟悉。]]></description>
            <content:encoded><![CDATA[<p>正如标题所言，在我四年的编程经历中就没刷过一道算法题，这可能与我所编写的应用有关，算法对我而言提升不是特别大。加上我几乎都是<strong>在需求中学习，而非系统性的学习</strong>。所以像算法这种基础知识我自然就不是很熟悉。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="那我为何会接触算法呢">那我为何会接触算法呢？<a href="https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte#%E9%82%A3%E6%88%91%E4%B8%BA%E4%BD%95%E4%BC%9A%E6%8E%A5%E8%A7%A6%E7%AE%97%E6%B3%95%E5%91%A2" class="hash-link" aria-label="那我为何会接触算法呢？的直接链接" title="那我为何会接触算法呢？的直接链接">​</a></h2>
<p>我在今年暑假期间有一个面试，当时面试官想考察下我的算法能力，而我直接明摆了说我不行（指算法上的不行），但面试官还是想继续考察，于是就出了道斐波那契数列作为考题。</p>
<p>但我毕竟也接触了 4 年的代码，虽说不刷算法，但起码也看过许多文章和代码，斐波那契数列使用递归实现的代码也有印象，于是很快我就写出了下面的代码作为我的答案。</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">fib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> n</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">fib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">+</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">fib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">2</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>面试官问我还有没有更好的答案，我便摇了摇头表示这 5 行不到的代码难道不是最优解？</p>
<blockquote>
<p>事实上这份代码看起来很简洁，实际却是耗时最慢的解法</p>
</blockquote>
<p>毫无疑问，在算法这关我肯定是挂了的，不过好在项目经验及后续的项目实践考核较为顺利，不然结局就是回去等通知了。最后面试接近尾声时，面试官友情提醒我加强基础知识（算法），强调各种应用框架不断更新迭代，但计算机的底层基础知识是不变的。于是在面试官的建议下，便有了本文。</p>
<p>好吧，我承认我是为了面试才去学算法的。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="对上述代码进行优化">对上述代码进行优化<a href="https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte#%E5%AF%B9%E4%B8%8A%E8%BF%B0%E4%BB%A3%E7%A0%81%E8%BF%9B%E8%A1%8C%E4%BC%98%E5%8C%96" class="hash-link" aria-label="对上述代码进行优化的直接链接" title="对上述代码进行优化的直接链接">​</a></h3>
<p>在介绍我是从何处学习算法以及从中学到了什么，不妨先来看看上题的最优答案是什么。</p>
<p>对于有接触过算法的同学而言，不难看出时间复杂度为 O(n²)，而指数阶属于爆炸式增长，当 n 非常大时执行效果缓慢，且可能会出现函数调用堆栈溢出。</p>
<p>如果仔细观察一下，会发现这其中进行了非常多的重复计算，我们不妨将设置一个 res 变量来输出一下结果</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">fib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> n</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> res </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">fib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">+</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">fib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">2</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token console class-name" style="color:hsl(35, 99%, 36%)">console</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">log</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">res</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> res</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>当 n=7 时，所输出的结果如下</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202309162220346.png" alt="Untitled" class="img_OpE3"></p>
<p>这还只是在 n=7 的情况下，便有这么多输出结果。而在算法中要避免的就是重复计算，这能够高效的节省执行时间，因此不妨定义一个缓存变量，在递归时将缓存变量也传递进去，如果缓存变量中存在则说明已计算过，直接返回计算结果即可。</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">fib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> mem </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> n</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">mem</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> mem</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> res </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">fib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> mem</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">+</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">fib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">2</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> mem</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">console</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">log</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">res</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  mem</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> res</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> res</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此时所输出的结果可以很明显的发现没有过多的重复计算，执行时间也有显著降低。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202309162220348.png" alt="Untitled" class="img_OpE3"></p>
<p>这便是<strong>记忆化搜索</strong>，时间复杂度被优化至 O(n)。</p>
<p>可这还是免不了递归调用出现堆栈溢出的情况（如 n=10000 时）。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202309162220349.png" alt="Untitled" class="img_OpE3"></p>
<p>从上面的解法来看，都是从”<strong>从顶至底</strong>”，比方说 n=7，会先求得 n=6 的结果, 而 n=6 又要求得 n=5 的结果，依次类推直至得到底层 n=1 的结果。</p>
<p>事实上我们可以换一种思路，先求得 n=1，n=2 的结果，然后依次类推上去，最终得到 n=6，n=7 的结果，也就是“<strong>从底至顶”</strong>，而这就是<strong>动态规划</strong>的方法。</p>
<p>从代码上来分析，因此我们可以初始化一个 dp 数组，用于存放数据状态。</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">fib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> dp </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">for</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">let</span><span class="token plain"> i </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">2</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"> i </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;=</span><span class="token plain"> n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"> i</span><span class="token operator" style="color:hsl(221, 87%, 60%)">++</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    dp</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> dp</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">i </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">+</span><span class="token plain"> dp</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">i </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">2</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> dp</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>最终 dp 数组的最后一个成员便是原问题的解。此时输出 dp 数组结果。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202309162220350.png" alt="Untitled" class="img_OpE3"></p>
<p>且由于不存在递归调用，因此你当 n=10000 时也不在会出现堆栈溢出的情况（只不过最终的结果必定超出了 JS 数值可表示范围，所以只会输出 Infinity）</p>
<p>对于上述代码而言，在空间复杂度上能够从 O(n) 优化到 O(1)，至于实现可以参考 <a href="https://www.hello-algo.com/chapter_dynamic_programming/intro_to_dynamic_programming#1414" target="_blank" rel="noopener noreferrer">空间优化</a>，这里便不再赘述。</p>
<p>我想至少从这里你就能看出算法的魅力所在，<strong>这里我强烈推荐 <a href="https://www.hello-algo.com/" target="_blank" rel="noopener noreferrer">hello-algo</a> 这本数据结构与算法入门书</strong>，我的算法之旅的起点便是从这本书开始，同时激发起我对算法的兴趣。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="两数之和">两数之和<a href="https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte#%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C" class="hash-link" aria-label="两数之和的直接链接" title="两数之和的直接链接">​</a></h2>
<p>于是在看完了这本算法书后，我便打开了大名鼎鼎的刷题网站 <a href="https://leetcode.cn/" target="_blank" rel="noopener noreferrer">LeetCode</a>，同时打开了究极经典题目的<a href="https://leetcode.cn/problems/two-sum" target="_blank" rel="noopener noreferrer">两数之和</a>。</p>
<blockquote>
<p>有人相爱，有人夜里开车看海，有人 leetcode 第一题都做不出来。</p>
</blockquote>
<p>题干：</p>
<blockquote>
<p>给定一个整数数组 <code>nums</code>&nbsp; 和一个整数目标值 <code>target</code>，请你在该数组中找出和为目标值 <code>target</code> 的那 <strong>两个</strong> 整数，并返回它们的数组下标。</p>
<p>你可以假设每种输入只会对应一个答案。但是，数组中同一个元素在答案里不能重复出现。</p>
<p>你可以按任意顺序返回答案。</p>
</blockquote>
<p>以下代码将会采用 JavaScript 代码作为演示。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="暴力枚举">暴力枚举<a href="https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte#%E6%9A%B4%E5%8A%9B%E6%9E%9A%E4%B8%BE" class="hash-link" aria-label="暴力枚举的直接链接" title="暴力枚举的直接链接">​</a></h3>
<p>我初次接触该题也只会暴力解法，遇事不决，暴力解决。也很验证了那句话：不论多久过去，我首先还是想到两个 for。</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">var</span><span class="token plain"> </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">twoSum</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> target</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> n </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">length</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">for</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">let</span><span class="token plain"> i </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"> i </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain"> n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"> i</span><span class="token operator" style="color:hsl(221, 87%, 60%)">++</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">for</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">let</span><span class="token plain"> j </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"> j </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain"> n</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"> j</span><span class="token operator" style="color:hsl(221, 87%, 60%)">++</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">+</span><span class="token plain"> nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">j</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">===</span><span class="token plain"> target </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&amp;&amp;</span><span class="token plain"> i </span><span class="token operator" style="color:hsl(221, 87%, 60%)">!==</span><span class="token plain"> j</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> j</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>当然针对上述 for 循环优化部分，比如说让 <code>j = i + 1</code> ，这样就可以有效避免重复数字的循环以及 <code>i ≠ j</code> 的判断。由于用到了两次循环，很显然时间复杂度为 O(n²)，并不高效。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="哈希表">哈希表<a href="https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte#%E5%93%88%E5%B8%8C%E8%A1%A8" class="hash-link" aria-label="哈希表的直接链接" title="哈希表的直接链接">​</a></h3>
<p>我们不妨将每个数字通过 hash 表缓存起来，将值 <code>nums[i]</code> 作为 key，将 <code>i</code> 作为 value。由于题目的条件则是 <code>x + y = target</code>，也就是 <code>target - x = y</code>，这样判断的条件就可以由 <code>nums[i]+ nums[j] === target</code> 变为 <code>map.has(target - nums[i])</code> 。如果 map 表中有 y 索引，那么显然 <code>target - nums[i] = y</code>，取出 y 的索引以及当前 i 索引就能够得到答案。代码如下</p>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">var</span><span class="token plain"> </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">twoSum</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> target</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> map </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Map</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">for</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">let</span><span class="token plain"> i </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"> i </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain"> nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">length</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"> i</span><span class="token operator" style="color:hsl(221, 87%, 60%)">++</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">map</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">has</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">target </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">map</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">target </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> i</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    map</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">set</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> i</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>而这样由于只有一次循环，时间复杂度为 O(N)。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="双指针算法特殊情况">双指针算法(特殊情况)<a href="https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte#%E5%8F%8C%E6%8C%87%E9%92%88%E7%AE%97%E6%B3%95%E7%89%B9%E6%AE%8A%E6%83%85%E5%86%B5" class="hash-link" aria-label="双指针算法(特殊情况)的直接链接" title="双指针算法(特殊情况)的直接链接">​</a></h3>
<p>假如理想情况下，题目所给定的 nums 是<strong>有序的情况</strong>，那么就可以考虑使用双指针解法。先说原理，假设给定的 nums 为 <code>[2,3,5,6,8]</code>，而目标的解为 9。在上面的做法中都是从索引 0 开始枚举，也就是 2,3,5…依次类推，如果没找到与 2 相加的元素则从 3 开始 3,5,6…依次类推。</p>
<p>此时我们不妨从<strong>最小的数</strong>和<strong>最大的数</strong>开始，在这个例子中也就是 2 和 8，很显然 <code>2 + 8 &gt; 9</code>，说明什么？说明 8 和中间所有数都大于 9 即 3+8 ，5+8 肯定都大于 9，所以 8 的下标必然不是最终结果，那么我们就可以把 8 排除，从 <code>[2,3,5,6]</code> 中找出结果，同样的从最小和最大的数开始，<code>2 + 6 &lt; 9</code> ，这又说明什么？说明 2 和中间这些数相加肯定都下雨 9 即 2+3，2+5 肯定都小于 9，因此 2 也应该排除，然后从 <code>[3,5,6]</code> 中找出结果。就这样依次类推，直到找到最终两个数 <code>3 + 6 = 9</code>，返回 3 与 6 的下标即可。</p>
<p>由于此解法相当于有两个坐标(指针)不断地向中间移动，因此这种解法也叫<strong>双指针算法</strong>。当然，要使用该方式的前提是输入的<strong>数组有序</strong>，否则无法使用。</p>
<p>用代码的方式来实现：</p>
<ol>
<li>定义两个坐标(指针)分别指向数组成员最左边与最右边，命名为 left 与 right。</li>
<li>使用 while 循环，循环条件为 left &lt; right。</li>
<li>判断 <code>nums[left] + nums[right]</code> 与 <code>target</code> 的大小关系，如果相等则说明找到目标(答案)，如果大于则 右指针减 1 <code>right—-</code>，小于则左指针加 1 <code>left++</code>。</li>
</ol>
<div class="language-tsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-tsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">twoSum</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> target</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">let</span><span class="token plain"> left </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">let</span><span class="token plain"> right </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">length</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">while</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">left </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain"> right</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> sum </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">left</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">+</span><span class="token plain"> nums</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">right</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">sum </span><span class="token operator" style="color:hsl(221, 87%, 60%)">===</span><span class="token plain"> target</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">left</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> right</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">sum </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"> target</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      right</span><span class="token operator" style="color:hsl(221, 87%, 60%)">--</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">else</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">sum </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain"> target</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      left</span><span class="token operator" style="color:hsl(221, 87%, 60%)">++</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<hr>
<p>针对上述两道算法题浅浅的做个分享，毕竟我还只是一名初入算法的小白。对我而言，我的算法刷题之旅还有很长的一段时间。且看样子这条路可能不会太平坦。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="算法对我有用吗">算法对我有用吗？<a href="https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte#%E7%AE%97%E6%B3%95%E5%AF%B9%E6%88%91%E6%9C%89%E7%94%A8%E5%90%97" class="hash-link" aria-label="算法对我有用吗？的直接链接" title="算法对我有用吗？的直接链接">​</a></h2>
<p>在我刷算法之前，我在网上看到鼓吹算法无用论的人，也能看到学算法却不知如何应用的人。</p>
<p>这也不禁让我思考 🤔，算法对我所开发的应用是否真的有用呢？</p>
<p>在我的开发过程中，往往面临着各种功能需求，而通常情况下我会以尽可能快的速度去实现该功能，至于说这个功能耗时 1ms，还是 100 ms，并不在乎。因为对我来说，这种微小的速度变化并不会被感知到，或者说绝大多数情况下，处理的数据规模都处在 n = 1 的情况下，此时我们还会在意 n² 大还是 2ⁿ 大吗？</p>
<p>但如果说到了用户感知到卡顿的情况下，那么此时才会关注性能优化，否则，过度的优化可能会成为一种徒劳的努力。</p>
<p>或许正是因为我都没有用到算法解决实际问题的经历，所以很难说服自己算法对我的工作有多大帮助。但不可否认的是，算法对我当前而言是一种思维上的拓宽。让我意识到一道（实际）问题的解法通常不只有一种，如何规划设计出一个高效的解决方案才是值得我们思考的地方。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="结语">结语<a href="https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte#%E7%BB%93%E8%AF%AD" class="hash-link" aria-label="结语的直接链接" title="结语的直接链接">​</a></h2>
<p>借 MIT 教授 Erik Demaine 的一句话</p>
<blockquote>
<p>If you want to become a good programmer, you can spend 10 years programming, or spend 2 years programming and learning algorithms.</p>
</blockquote>
<p>如果你想成为一名优秀的程序员，你可以花 10 年时间编程，或者花 2 年时间编程和学习算法。</p>
<p>这或许就是学习算法的真正意义。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="参考文章">参考文章<a href="https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte#%E5%8F%82%E8%80%83%E6%96%87%E7%AB%A0" class="hash-link" aria-label="参考文章的直接链接" title="参考文章的直接链接">​</a></h2>
<p><a href="https://www.hello-algo.com/chapter_dynamic_programming/intro_to_dynamic_programming" target="_blank" rel="noopener noreferrer">初探动态规划</a></p>
<p><a href="https://www.zhihu.com/question/335097718" target="_blank" rel="noopener noreferrer">学习算法重要吗?</a></p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>算法</category>
        </item>
        <item>
            <title><![CDATA[Nest grpc 实践之调用 python ddddocr 库]]></title>
            <link>https://kuizuo.me/blog/nest-grpc-ocr</link>
            <guid>https://kuizuo.me/blog/nest-grpc-ocr</guid>
            <pubDate>Sat, 29 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[本文将使用 nest 通过 grpc 的方式来调用 python 的 ddddocr 库来识别验证码。]]></description>
            <content:encoded><![CDATA[<p>我曾经写过一个项目 <a href="https://github.com/kuizuo/ddddocr_server" target="_blank" rel="noopener noreferrer">ddddocr_server</a>，使用 fastapi 提供 http 接口，以此来调用 <a href="https://github.com/sml2h3/ddddocr" target="_blank" rel="noopener noreferrer">ddddocr</a> 库。</p>
<p>其他语言想要调用的话，则是通过 http 协议的方式来调用。然而 http 协议的开销不小，而 Websocket 调用又不灵活，此时针对这种应用场景的最佳选择就是 rpc（Remote Procedure Call 远程过程调用），而这次所要用的技术便是 grpc。</p>
<p>早闻 <a href="https://grpc.io/" target="_blank" rel="noopener noreferrer">gRPC</a> 大名，所以这次将使用 nest 通过 grpc 的方式来调用 python 的 ddddocr 库来识别验证码。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="效果图">效果图<a href="https://kuizuo.me/blog/nest-grpc-ocr#%E6%95%88%E6%9E%9C%E5%9B%BE" class="hash-link" aria-label="效果图的直接链接" title="效果图的直接链接">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823586.png" alt="Untitled" class="img_OpE3"></p>
<p>本文源码 <a href="https://github.com/kuizuo/nest-ocr" target="_blank" rel="noopener noreferrer">nest-ocr</a></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="简单熟悉下-grpc">简单熟悉下 grpc<a href="https://kuizuo.me/blog/nest-grpc-ocr#%E7%AE%80%E5%8D%95%E7%86%9F%E6%82%89%E4%B8%8B-grpc" class="hash-link" aria-label="简单熟悉下 grpc的直接链接" title="简单熟悉下 grpc的直接链接">​</a></h2>
<p>由于我们的调用方是 nest，因此就很有必要熟悉一下 nest 要如何创建</p>
<p>官方提供了一个 <a href="https://github.com/nestjs/nest/tree/master/sample/04-grpc" target="_blank" rel="noopener noreferrer">样例</a>，本文便在此基础上进行更改。</p>
<p>首先，在 nest 中 grpc 是以微服务的方式启动的，从代码上也就 3 行便可实现。</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>main.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> app </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> NestFactory</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">create</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">AppModule</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">connectMicroservice </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  MicroserviceOptions </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    transport</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Transport</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">GRPC</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    options</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">package</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hero'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      protoPath</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">join</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'./hero/hero.proto'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> app</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">startAllMicroservices</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>既然服务有了，那么要如何调用呢？或者说有没有像 http 接口调试工具能够调用 grpc 服务，有很多种 grpc 客户端工具，但这里选择 Postman。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823587.png" alt="Untitled" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="创建-api">创建 API<a href="https://kuizuo.me/blog/nest-grpc-ocr#%E5%88%9B%E5%BB%BA-api" class="hash-link" aria-label="创建 API的直接链接" title="创建 API的直接链接">​</a></h3>
<p>不过这里先别急着调用，为了后续调试，建议先到工作区的 APIs 中添加一个 API，然后将样例中的 hero.proto 中导入进来</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823588.png" alt="Untitled" class="img_OpE3"></p>
<p>导入完毕后将显示如下页面</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823589.png" alt="Untitled" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="创建-grpc-客户端">创建 gRPC 客户端<a href="https://kuizuo.me/blog/nest-grpc-ocr#%E5%88%9B%E5%BB%BA-grpc-%E5%AE%A2%E6%88%B7%E7%AB%AF" class="hash-link" aria-label="创建 gRPC 客户端的直接链接" title="创建 gRPC 客户端的直接链接">​</a></h3>
<p>点击工作区旁边的 New 按钮（不是 + 按钮），选择 gRPC</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823590.png" alt="Untitled" class="img_OpE3"></p>
<p>在 Enter URL 输入框填写 <a href="http://localhost:5000/" target="_blank" rel="noopener noreferrer">localhost:5000</a> (nest grpc 默认地址)，这里你也可以选择第一个官方的 gRPC 测试服务，用于看看效果。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823591.png" alt="Untitled" class="img_OpE3"></p>
<p>填写完毕后，你会发现在右侧 Select a method 中并没有看到所定义的两个方法：FindOne，FindMang，这时候我们需要将 hero.proto 文件导入进来，如果你完成了 创建 API 那一步骤，你在右侧便能看到那两个方法</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823592.png" alt="Untitled" class="img_OpE3"></p>
<p>此时不妨选择一下 FindOne，然后点击下方 Use Example Message，将 id 填为 1，点击 Invoke，得到的效果图如下。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823593.png" alt="Untitled" class="img_OpE3"></p>
<p>到这里我们就已经搞定了如何调用 grpc 服务，接下来就要自己去实现标题的需求。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="protobuf-消息编码">Protobuf 消息编码<a href="https://kuizuo.me/blog/nest-grpc-ocr#protobuf-%E6%B6%88%E6%81%AF%E7%BC%96%E7%A0%81" class="hash-link" aria-label="Protobuf 消息编码的直接链接" title="Protobuf 消息编码的直接链接">​</a></h2>
<p><strong>在 grpc 中，数据传输部分通过 Protobuf（Protocol Buffers）定义</strong></p>
<p>因为从上面服务调用来看，貌似与 http 协议调用不相上下。</p>
<p>其实不然，protobuf 不同于 JSON、XML 数据，是以二进制数据流传输，数据在经 protobuf 序列化后的消息体积很小（传输内容少，传输相对就快）。同时在加上 HTTP/2 协议的加持（底层传输协议，可替换为其他协议），使得 gRPC 的传输性能要优于传统 Restful。</p>
<p>protobuf 对于数据传输的优点有很多，如 <strong>支持流式传输，不过这就不是本文所述的内容了。总之你只要知道 grpc 性能高的原因就是因为 protobuf。</strong></p>
<div class="language-protobuf codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>hero.proto<span style="flex:1;text-align:right">protobuf</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-protobuf codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">syntax</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"proto3"</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">package</span><span class="token plain"> hero</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">service</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">HeroService</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">rpc</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">FindOne</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token class-name" style="color:hsl(35, 99%, 36%)">HeroById</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">returns</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Hero</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">rpc</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">FindMany</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">stream</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">HeroById</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">returns</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">stream</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Hero</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">message</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">HeroById</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">int32</span><span class="token plain"> id </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">message</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Hero</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">int32</span><span class="token plain"> id </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token plain"> name </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">2</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>不难看出，package 定义包名，service 定义服务，而 message 则是定义数据传输的类型。</p>
<p>客户端与服务端将根据 protobuf 来生成双方交互方式，其中包名决定了双方传输的作用域，service 下的函数就是双方之间的预先定义好要以什么样的数据发送，又以什么样的数据返回。</p>
<p>我个人是觉得没什么特别重点的部分，根据自己的需求然后修改基本数据结构便可。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="实践">实践<a href="https://kuizuo.me/blog/nest-grpc-ocr#%E5%AE%9E%E8%B7%B5" class="hash-link" aria-label="实践的直接链接" title="实践的直接链接">​</a></h2>
<p>首先，要<strong>明确谁是客户端，谁是服务端。</strong></p>
<p>从 标题 上来看，不难看出是 js(client) ⇒ python(server)，也就是 nest 调用 ddddocr 这个库，那么 nest 就应该作为客户端，而 python 作为服务端。</p>
<p>先将整个流程先捋一遍，如图下图示意。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823594.png" alt="Untitled" class="img_OpE3"></p>
<p>用户想要调用 ddddocr 库，最理想的肯定是让用户直接和 python 打交道，但应用（这里指 Web）通常不会使用 python 进行编写，而其他语言(js)想要跨语言调用，这时 rpc 就再适合不过了。</p>
<p>可能会有人说这么操作多此一举，我只能说根据性能和业务为主。相比将 nest 后端服务迁移到 python 上，和在 nest 与 python 之间多层 grpc，在两者的工作量之下我肯定毫不疑问的选择后者。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="protobuf-定义">protobuf 定义<a href="https://kuizuo.me/blog/nest-grpc-ocr#protobuf-%E5%AE%9A%E4%B9%89" class="hash-link" aria-label="protobuf 定义的直接链接" title="protobuf 定义的直接链接">​</a></h3>
<div class="language-protobuf codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>ocr.proto<span style="flex:1;text-align:right">protobuf</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-protobuf codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">syntax</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"proto3"</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">package</span><span class="token plain"> ocr</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">service</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">OCR</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">rpc</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">Character</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token class-name" style="color:hsl(35, 99%, 36%)">CharacterBody</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">returns</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token class-name" style="color:hsl(35, 99%, 36%)">CharacterReply</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// TODO: Add other type, e.g. select, slide, etc.</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">message</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">CharacterBody</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">bytes</span><span class="token plain"> image </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">message</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">CharacterReply</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token plain"> result </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">int32</span><span class="token plain"> consumedTime </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">2</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>这部分没什么特别好说的，就图片数据以字节数组的方式传递。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="nest-部分">nest 部分<a href="https://kuizuo.me/blog/nest-grpc-ocr#nest-%E9%83%A8%E5%88%86" class="hash-link" aria-label="nest 部分的直接链接" title="nest 部分的直接链接">​</a></h3>
<p>由于 nest 作为客户端，事实上示例部分的很多代码都无关了，就比如 main.ts 中用于启动 gRPC 服务的代码，都可以注释掉，因为在这里我们并不打算将 nest 作为服务端。</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>main.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token comment" style="color:hsl(230, 4%, 64%)">// app.connectMicroservice&lt;MicroserviceOptions&gt;(grpcClientOptions);</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token comment" style="color:hsl(230, 4%, 64%)">// await app.startAllMicroservices();</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>最核心的代码，就是定义 client, 如下</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Client</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  transport</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Transport</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">GRPC</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  options</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">package</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token string" style="color:hsl(119, 34%, 47%)">'ocr'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    protoPath</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">join</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'./ocr.proto'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    url</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'localhost:50051'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// 这里所定义的是 grpc 服务端地址</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">client</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> ClientGrpc</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<blockquote>
<p>这一部分也可以通过构造函数的方式注入，因人而异。 <code>constructor(@Inject('OCR_PACKAGE') private readonly client: ClientGrpc) {}</code></p>
</blockquote>
<p>有了这个 client 就能够获取 ocrService 了，完整 ocr.controller.ts 代码如下</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>ocr.controller.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> Body</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> Controller</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> OnModuleInit</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> Post </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@nestjs/common'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> Client</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> ClientGrpc </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@nestjs/microservices'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> Observable </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'rxjs'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> Character </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'./interfaces/character.interface'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> Reply </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'./interfaces/reply.interface'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> grpcClientOptions </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'src/grpc-client.options'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> CharacterDto </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'./dtos/character.dto'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">interface</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">OCRService</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">Character</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">image</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Character</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Observable</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">Reply</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// TODO: Add other type, e.g. select, slide, etc.</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Controller</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'ocr'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">OcrController</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">implements</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">OnModuleInit</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">private</span><span class="token plain"> ocrService</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> OCRService</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Client</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">grpcClientOptions</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  client</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> ClientGrpc</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">onModuleInit</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">ocrService </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">client</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">getService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'OCR'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Post</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'character'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">character</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Body</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> dto</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> CharacterDto</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Observable</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">Reply</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// 这里多一步 Base64 将文本解码成图片的操作</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// 主要是根据接口易用性而定，最佳的做法肯定是类似上传文件，直接得到图片二进制数据，省去数据操作步骤</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> buffer </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> Buffer</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">from</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">dto</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">image</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'base64'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">ocrService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">Character</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> image</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> buffer </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// TODO: Add other type, e.g. select, slide, etc.</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>而在之前 http 的方式实现的话，这里 <code>this.ocrService.Character({ image: dto.image });</code> 所对应的就是例如 <code>fetch(’http://localhost:3002/ocr/character’)</code> ，这里 3002 端口对应的是 python 的 http 服务。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="python-部分">python 部分<a href="https://kuizuo.me/blog/nest-grpc-ocr#python-%E9%83%A8%E5%88%86" class="hash-link" aria-label="python 部分的直接链接" title="python 部分的直接链接">​</a></h3>
<p>服务端部分其实还稍微有些复杂，可能是因为我太久没写 python 的缘故。</p>
<p>在之前是通过 python 来启动一个 http 服务来供其他语言调用，现在有了 gRPC 就完全没必要启动 http 服务。</p>
<p>可以在 <a href="https://grpc.io/docs/languages/python/quickstart/#download-the-example" target="_blank" rel="noopener noreferrer">这里</a> 下载官方的 python 示例。</p>
<p>先安装 grpc_tools</p>
<div class="language-bash codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-bash codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">python3 </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">-m</span><span class="token plain"> pip </span><span class="token function" style="color:hsl(221, 87%, 60%)">install</span><span class="token plain"> grpcio-tools</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>接着执行下方指令</p>
<div class="language-bash codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-bash codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">python3 </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">-m</span><span class="token plain"> grpc_tools.protoc </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">-I</span><span class="token plain"> </span><span class="token builtin class-name" style="color:hsl(35, 99%, 36%)">.</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">--python_out</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain">. </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">--grpc_python_out</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain">. ocr.proto</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>它将会在下方根据 <code>ocr.proto</code> 生成 <code>ocr_pb2.py</code> 与 <code>ocr_pb2_grpc.py</code> 两个文件，事实上这两个文件都无需改动，你只需要每次修改 .proto 文件后再重新执行上方代码将新的内容复写到文件上便可。</p>
<p>不过要搞清流程，还要是在意这些文件便可。其中在 <code>ocr_pb2_grpc.py</code> 文件中，你会找到 OCRServicer 类的接口定义。</p>
<div class="language-python codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>ocr_pb2_grpc.py<span style="flex:1;text-align:right">python</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-python codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">OCRServicer</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token builtin" style="color:hsl(119, 34%, 47%)">object</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:hsl(119, 34%, 47%)">"""Missing associated documentation comment in .proto file."""</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">def</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">Character</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> request</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> context</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token triple-quoted-string string" style="color:hsl(119, 34%, 47%)">"""Missing associated documentation comment in .proto file."""</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        context</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">set_code</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">grpc</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">StatusCode</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">UNIMPLEMENTED</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        context</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">set_details</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'Method not implemented!'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">raise</span><span class="token plain"> NotImplementedError</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'Method not implemented!'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>很显然这是一个接口类，因此我们需要实现它。</p>
<p>而 ocr_pb2.py 内容就不必细看，但后续也需要用到，主要通过 <code>ocr_pb2.CharacterReply</code> 将数据封装返回给客户端。</p>
<p>最终完整的 <a href="http://server.py/" target="_blank" rel="noopener noreferrer">server.py</a> 内容如下</p>
<div class="language-python codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>server.py<span style="flex:1;text-align:right">python</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-python codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> concurrent </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> futures</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> time</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> grpc</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> ocr_pb2</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> ocr_pb2_grpc</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> ddddocr</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">ocr </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> ddddocr</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">DdddOcr</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">beta</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token boolean" style="color:hsl(35, 99%, 36%)">True</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">OCRServicer</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">ocr_pb2_grpc</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">OCRServicer</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># 这里实现 英数验证码 识别</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">def</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">Character</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> request</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> context</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        t </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> time</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">perf_counter</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        result </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> ocr</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">classification</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">image</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        consumed_time </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">int</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">time</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">perf_counter</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain"> t</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token operator" style="color:hsl(221, 87%, 60%)">*</span><span class="token number" style="color:hsl(35, 99%, 36%)">1000</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">print</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token string" style="color:hsl(119, 34%, 47%)">'result'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">:</span><span class="token plain"> result</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'consumedTime'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">:</span><span class="token plain"> consumed_time</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># 根据 ocr.proto 的 message CharacterReply 生成的类</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        response </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> ocr_pb2</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">CharacterReply</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">            result</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain">result</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> consumedTime</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain">consumed_time</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> response</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">def</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">serve</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    port </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'50051'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    server </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> grpc</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">server</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">futures</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">ThreadPoolExecutor</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">max_workers</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token number" style="color:hsl(35, 99%, 36%)">10</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    ocr_pb2_grpc</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">add_OCRServicer_to_server</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">OCRServicer</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> server</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    server</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">add_insecure_port</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'[::]:'</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">+</span><span class="token plain"> port</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    server</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">start</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">print</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">"Server started, listening on "</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">+</span><span class="token plain"> port</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    server</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">wait_for_termination</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> __name__ </span><span class="token operator" style="color:hsl(221, 87%, 60%)">==</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'__main__'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    serve</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此时整个代码的核心流程就已经搞通了，你可以到 <a href="https://github.com/kuizuo/nest-ocr" target="_blank" rel="noopener noreferrer">nest-ocr</a> 查看源码，先看看用 postman grpc 方式调用，这里 image 为 字节数组（图片的二进制数据）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823596.png" alt="Untitled" class="img_OpE3"></p>
<p>用户以 http 方式访问的效果。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823595.png" alt="Untitled" class="img_OpE3"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="结语">结语<a href="https://kuizuo.me/blog/nest-grpc-ocr#%E7%BB%93%E8%AF%AD" class="hash-link" aria-label="结语的直接链接" title="结语的直接链接">​</a></h2>
<p>时间因素，因此本文最终代码都仅实现 <strong>英数字符识别</strong>，ddddocr 还支持点选、滑块，如有时间再补充相关代码。</p>
<p>从 http 方式转到 gRPC 无非就是围绕 protobuf 展开，预先定义好 protobuf，然后在此基础上去编写 grpc 客户端(调用方)与服务端(提供方) 的代码。虽然引入了一丝复杂性，但可以有效提高性能。</p>
<p>有时候，为了优化性能，又不想增加硬件开销，我们不得不在代码层面做出一些改进，更换高性能框架便是其中之一。然而事实上，提高性能最快捷的方式就是升级硬件。并发数不足，增加服务器数量是最直接有效的办法。</p>
<p>为了偏薄的性能提升，开发者总能想出诸多的解决方案。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>nest</category>
            <category>grpc</category>
            <category>python</category>
            <category>ddddocr</category>
        </item>
        <item>
            <title><![CDATA[nest.js 添加 swagger 响应数据文档]]></title>
            <link>https://kuizuo.me/blog/nest-swagger-response-data</link>
            <guid>https://kuizuo.me/blog/nest-swagger-response-data</guid>
            <pubDate>Tue, 18 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[nest.js 添加 swagger 响应数据文档]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="基本使用">基本使用<a href="https://kuizuo.me/blog/nest-swagger-response-data#%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="基本使用的直接链接" title="基本使用的直接链接">​</a></h2>
<p>通常情况下，在 nest.js 的 swagger 页面文档中的响应数据文档默认如下</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307180105813.png" alt="" class="img_OpE3"></p>
<p>此时要为这个控制器添加响应数据文档的话，只需要先声明 数据的类型，然后通过@ApiResponse 装饰器添加到该控制器上即可，举例说明</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>todo.entity.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Entity</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'todo'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">TodoEntity</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Column</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiProperty</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> description</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'todo'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  value</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiProperty</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> description</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'todo'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Column</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token boolean" style="color:hsl(35, 99%, 36%)">false</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  status</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">boolean</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>todo.controller.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiOperation</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> summary</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'获取Todo详情'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiResponse</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">TodoEntity</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">list</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">Promise</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">TodoEntity</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">todoService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">list</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">':id'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiOperation</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> summary</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'获取Todo详情'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiResponse</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> TodoEntity </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">info</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">IdParam</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">number</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">Promise</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">TodoEntity</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">todoService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">detail</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此时对应的文档数据如下显示</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307180122728.png" alt="image-20230718012234692" class="img_OpE3"></p>
<p>如果你想要自定义返回的数据，而不是用 entity 对象的话，可以按照如下定义</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>todo.model.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Todo</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiProperty</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> description</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'todo'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  value</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiProperty</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> description</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'todo'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  status</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">boolean</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>然后将 <code>@ApiResponse({ type: TodoEntity })</code> 中的 <code>TodoEntity</code> 替换 <code>Todo</code> 即可。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="自定义返回数据">自定义返回数据<a href="https://kuizuo.me/blog/nest-swagger-response-data#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%BF%94%E5%9B%9E%E6%95%B0%E6%8D%AE" class="hash-link" aria-label="自定义返回数据的直接链接" title="自定义返回数据的直接链接">​</a></h2>
<p>然而通常情况下，都会对返回数据进行一层包装，如</p>
<div class="language-json codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-json codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token property" style="color:hsl(5, 74%, 59%)">"data"</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token property" style="color:hsl(5, 74%, 59%)">"name"</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"string"</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token property" style="color:hsl(5, 74%, 59%)">"code"</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">200</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token property" style="color:hsl(5, 74%, 59%)">"message"</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"success"</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>其中 data 数据就是原始数据。要实现这种数据结构字段，首先定义一个自定义类用于包装，如</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_">res.model.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">ResOp</span><span class="token class-name operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token class-name constant" style="color:hsl(35, 99%, 36%)">T</span><span class="token class-name" style="color:hsl(35, 99%, 36%)"> </span><span class="token class-name operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token class-name" style="color:hsl(35, 99%, 36%)"> </span><span class="token class-name builtin" style="color:hsl(119, 34%, 47%)">any</span><span class="token class-name operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiProperty</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'object'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  data</span><span class="token operator" style="color:hsl(221, 87%, 60%)">?</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token constant" style="color:hsl(35, 99%, 36%)">T</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiProperty</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'number'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">200</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  code</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">number</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">ApiProperty</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'string'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'success'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  message</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">constructor</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">code</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">number</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> data</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token constant" style="color:hsl(35, 99%, 36%)">T</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> message </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'success'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">code </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> code</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">data </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> data</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">this</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">message </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> message</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>接着在定义一个拦截器，将 data 数据用 ResOp 包装，如下拦截器代码如下</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>transform.interceptor.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">class</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">TransformInterceptor</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">implements</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">NestInterceptor</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">constructor</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">private</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">readonly</span><span class="token plain"> reflector</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Reflector</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">intercept</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">context</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> ExecutionContext</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> next</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> CallHandler</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token builtin" style="color:hsl(119, 34%, 47%)">any</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Observable</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token builtin" style="color:hsl(119, 34%, 47%)">any</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> next</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">handle</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">pipe</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token function" style="color:hsl(221, 87%, 60%)">map</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">data </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> response </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> context</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">switchToHttp</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token generic-function function" style="color:hsl(221, 87%, 60%)">getResponse</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token generic-function generic class-name" style="color:hsl(35, 99%, 36%)">FastifyReply</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        response</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">header</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'Content-Type'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'application/json; charset=utf-8'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">ResOp</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">HttpStatus</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">OK</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> data </span><span class="token operator" style="color:hsl(221, 87%, 60%)">??</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">null</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此时返回的数据都会转换为 <code>{ "data": { }, "code": 200, "message": "success" }</code> 的形式，这部分不为就本文重点，就不赘述了。</p>
<p>回到 Swagger 文档中，只需将 <code>@ApiResponse({ type: TodoEntity })</code> 改写成 <code>@ApiResponse({ type: ResOp&lt;TodoEntity&gt; })</code>，就可以实现下图需求。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307180126751.png" alt="image-20230718012618710" class="img_OpE3"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="自定义-api-装饰器">自定义 Api 装饰器<a href="https://kuizuo.me/blog/nest-swagger-response-data#%E8%87%AA%E5%AE%9A%E4%B9%89-api-%E8%A3%85%E9%A5%B0%E5%99%A8" class="hash-link" aria-label="自定义 Api 装饰器的直接链接" title="自定义 Api 装饰器的直接链接">​</a></h2>
<p>然而对于庞大的业务而言，使用 <code>@ApiResponse({ type: ResOp&lt;TodoEntity&gt; })</code>的写法，肯定不如 <code>@ApiResponse({ type: TodoEntity })</code>来的高效，有没有什么办法能够用后者的写法，却能达到前者的效果，答案是肯定有的。</p>
<p>这里需要先自定义一个装饰器，命名为 <code>ApiResult</code>，完整代码如下</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>api-result.decorator.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> Type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> applyDecorators</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> HttpStatus </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@nestjs/common'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> ApiExtraModels</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> ApiResponse</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> getSchemaPath </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@nestjs/swagger'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> ResOp </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@/common/model/response.model'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> baseTypeNames </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token string" style="color:hsl(119, 34%, 47%)">'String'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'Number'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'Boolean'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token doc-comment comment" style="color:hsl(230, 4%, 64%)">/**</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token doc-comment comment" style="color:hsl(230, 4%, 64%)"> * </span><span class="token doc-comment comment keyword" style="color:hsl(301, 63%, 40%)">@description</span><span class="token doc-comment comment" style="color:hsl(230, 4%, 64%)">: 生成返回结果装饰器</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token doc-comment comment" style="color:hsl(230, 4%, 64%)"> */</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> ApiResult </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">TModel </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">extends</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Type</span><span class="token class-name operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token class-name builtin" style="color:hsl(119, 34%, 47%)">any</span><span class="token class-name operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  isPage</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  status</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">?</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> TModel </span><span class="token operator" style="color:hsl(221, 87%, 60%)">|</span><span class="token plain"> TModel</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  isPage</span><span class="token operator" style="color:hsl(221, 87%, 60%)">?</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">boolean</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  status</span><span class="token operator" style="color:hsl(221, 87%, 60%)">?</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> HttpStatus</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">let</span><span class="token plain"> prop </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">null</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token builtin" style="color:hsl(119, 34%, 47%)">Array</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">isArray</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">isPage</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      prop </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'object'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        properties</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          items</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">            type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'array'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">            items</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> $ref</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">getSchemaPath</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          meta</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">            type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'object'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">            properties</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">              itemCount</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'number'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">              totalItems</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'number'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">              itemsPerPage</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'number'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">              totalPages</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'number'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">              currentPage</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'number'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">else</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      prop </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'array'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        items</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> $ref</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">getSchemaPath</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">else</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">type </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&amp;&amp;</span><span class="token plain"> baseTypeNames</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">includes</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      prop </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">toLocaleLowerCase</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">else</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      prop </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> $ref</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">getSchemaPath</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">else</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    prop </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'null'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">null</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> model </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">Array</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">isArray</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">?</span><span class="token plain"> type</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token number" style="color:hsl(35, 99%, 36%)">0</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">type</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token class-name keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">applyDecorators</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token function" style="color:hsl(221, 87%, 60%)">ApiExtraModels</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">model</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token function" style="color:hsl(221, 87%, 60%)">ApiResponse</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      status</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      schema</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        allOf</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> $ref</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">getSchemaPath</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">ResOp</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">            properties</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">              data</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> prop</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">            </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>其核心代码就是在 <code>@ApiResponse</code> 上进行扩展，这一部分代码在官方文档: <a href="https://docs.nestjs.com/openapi/operations#advanced-generic-apiresponse" target="_blank" rel="noopener noreferrer">advanced-generic-apiresponse</a> 中提供相关示例，这里我简单说明下：</p>
<p><code>{ $ref: getSchemaPath(ResOp) }</code> 表示原始数据，要被“塞”到那个类下，而第二个参数 <code>properties: { data: prop }</code> 则表示 <code>ResOp</code> 的 <code>data</code> 属性要如何替换，替换的部分则由 <code>prop</code> 变量决定，只需要根据实际需求构造相应的字段结构。</p>
<p>由于有些类没有被任何控制器直接引用， SwaggerModule <code>SwaggerModule</code> 还无法生成相应的模型定义，所以需要 <code>@ApiExtraModels(model)</code> 将其额外导入。</p>
<p>此时只需要将 <code>@ApiResponse({ type: TodoEntity })</code> 改写为 <code>@ApiResult({ type: TodoEntity })</code>，就可达到最终目的。</p>
<p>不过我还对其进行扩展，使其能够返回分页数据格式，具体根据实际数据而定，演示效果如下图：</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307180237658.png" alt="image-20230718023729609" class="img_OpE3"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="导入第三方接口管理工具">导入第三方接口管理工具<a href="https://kuizuo.me/blog/nest-swagger-response-data#%E5%AF%BC%E5%85%A5%E7%AC%AC%E4%B8%89%E6%96%B9%E6%8E%A5%E5%8F%A3%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="导入第三方接口管理工具的直接链接" title="导入第三方接口管理工具的直接链接">​</a></h2>
<p>通过上述的操作后，此时记下项目的 swagger-ui 地址，例如 <a href="http://127.0.0.1:5001/api-docs" target="_blank" rel="noopener noreferrer">http://127.0.0.1:5001/api-docs</a>, 此时再后面添加<code>-json</code>，即 <a href="http://127.0.0.1:5001/api-docs-json" target="_blank" rel="noopener noreferrer">http://127.0.0.1:5001/api-docs-json </a> 所得到的数据便可导入到第三方的接口管理工具，就能够很好的第三方的接口协同，接口测试等功能。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307180226265.png" alt="image-20230718022612215" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307180224284.png" alt="image-20230718022446188" class="img_OpE3"></p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>nest</category>
            <category>swagger</category>
        </item>
        <item>
            <title><![CDATA[叙一名转专业+休学的大学生经历]]></title>
            <link>https://kuizuo.me/blog/narrate-a-college-student</link>
            <guid>https://kuizuo.me/blog/narrate-a-college-student</guid>
            <pubDate>Tue, 11 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[我一般很少做年中总结，但是这上半年发生在我事情比较多，加上毕业季，万千感慨涌上心头。]]></description>
            <content:encoded><![CDATA[<p>我一般很少做年中总结，但是这上半年发生在我事情比较多，加上毕业季，万千感慨涌上心头。</p>
<p>过得很快，本该在这个时间段毕业的我，因一意孤行申请休学一年，导致我比原同一届的人晚毕业一年。也正是这个决定改变了我的人生轨迹，让我成长了许多。</p>
<p>如今的我作为一名准大四的大学生 👨‍🎓，且经历过转专业和休学的大学生，来叙述自己的经历。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="前言">前言<a href="https://kuizuo.me/blog/narrate-a-college-student#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接链接" title="前言的直接链接">​</a></h2>
<p>如果你有注意到我的 <a href="https://github.com/kuizuo?tab=overview&amp;from=2023-07-01&amp;to=2023-07-19" target="_blank" rel="noopener noreferrer">github contributions</a>，你会发现我有整整一段时间（约 2 个月）没怎么编写代码和记录博客了。在这期间整个人的状态很差，十分消沉，迟迟到现在为止。</p>
<p>在这期间我被<strong>公安传唤</strong>了。说难听点，我当时的身份是犯罪嫌疑人。当然现在应该也算，处于取保候审状态，除了不能出国外，目前还算自由。</p>
<div class="theme-admonition theme-admonition-info admonition_fh9h alert alert--info"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>补充: 暑假期间还想去香港旅游，去办理港澳通行证的时候被拒绝了 🤡。还想着去尝试办理 visa 卡（可境外金额交易），现在看样子结果已经毋庸置疑了 😔。</div></div>
<p>至于案情我不方便细说，总之与互联网（技术）相关且在我休学期间出的事情。一开始关于这个话题我并不想提及，毕竟说出来肯定对自身有所不好，因此本文便迟迟都处于草稿状态。但结合自身经历，我认为非常有必要把这些内容记录下来，避免重蹈覆辙。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="转专业休学">转专业+休学<a href="https://kuizuo.me/blog/narrate-a-college-student#%E8%BD%AC%E4%B8%93%E4%B8%9A%E4%BC%91%E5%AD%A6" class="hash-link" aria-label="转专业+休学的直接链接" title="转专业+休学的直接链接">​</a></h2>
<p>我想大学内有这两项经历的人应该是在少数，我会把缘由交代明细。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="转专业">转专业<a href="https://kuizuo.me/blog/narrate-a-college-student#%E8%BD%AC%E4%B8%93%E4%B8%9A" class="hash-link" aria-label="转专业的直接链接" title="转专业的直接链接">​</a></h3>
<p>我大一时的专业是 《信息与科学计算》，所属数学系，这个并不是计算机专业（虽然涉及到一些计算机相关的课程，但本质还是理学，不是工学），所以我转专业的原因很简单，就是要<strong>科班出身</strong>。当时就我看来科班出身能为我以后工作带来很大的帮助（当时的想法很天真，没有考虑转专业后所要付出的代价），但在这些年的经历以及行内的各个大神的能力告诉我，实际上并不会带来多大的优势。</p>
<p>我转专业并不难，因为我暑假就已经接触编程，并且在大一的时候，每天不是在写代码，就是在看代码，那段时间可谓是我人生编程学习最快乐的时光。所以转专业的考试对我来说特别轻松加上我数学天赋还不错，原专业的成绩也 OK，屁颠屁颠地将转专业填报单提交了上去，下学期便分配到新的班级中修学新专业——软件工程。</p>
<p>然而就在我转完专业，我便开始后悔转专业了。我发现学校老师所教的是什么牛马？真的就是会念 PPT 就便会教课，而且所教的多数内容，所发放的题材都是相对过时甚至被淘汰的东西。难怪说学生会对编程失去兴趣，要是我一开始跟着老师这么学，现在那有愧怍二字。</p>
<p>课程教学质量差也就算了，课程设计的要求还与学生课堂的内容还不同，我很难想象这学期只教 Web 前端（ES5 时代），却要求课程设计实现一个带后端接口服务以及数据库服务的程序。要不是我当时的基础还算好，恐怕连项目买来都不知道怎么跑起来。</p>
<p>不过这也是国内绝大多数高校现状，课程内容老旧，教案设计不合理，在越垃圾的大学中，这种情况反而更明显，恰好我就读于垃圾中的垃圾。与其抱怨教学质量，不如自己潜心学习，也正是因如此，自我学习能力才能有所大提升。</p>
<p>也不能说转专业对我没有一点帮助，毕竟自身有了一定的编程基础知识，在专业课上回答问题上我还是能说上一点的。并且每次课程设计与考试的时候总有人会找我来报个大腿，老师也见识到我的专业能力，我这一小组成员都能轻松通过课设答辩。（主要还是归功于我吹牛逼的能力）</p>
<p>关于转专业，还有一点就是补修。比如我大一是数学系的，当时的课是叫数学分析，而在软件工程专业就是传统的高等数学，运气还算好，这两门类别相同，可以做学分替换。但往往没那么幸运，就需要额外花时间去修之前的软件工程大一的课程，在跨度较大的转专业中甚至还会更多。</p>
<div class="theme-admonition theme-admonition-warning admonition_fh9h alert alert--warning"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>警示</div><div class="admonitionContent_oz3Y"><p>最后我想告诫一些有想转专业的同学，如果你能接受转专业的麻烦，并且真的认为转专业对你带来帮助，那可以转。但如果只是为了换个班级换个室友什么的，转后的代价或许比你与同学间不友好相处四年还要负重。<strong>总之，转与不转，最终目的是以最快捷轻松的方式拿毕业证为主。</strong></p></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="休学">休学<a href="https://kuizuo.me/blog/narrate-a-college-student#%E4%BC%91%E5%AD%A6" class="hash-link" aria-label="休学的直接链接" title="休学的直接链接">​</a></h3>
<p>然后在我大二上的时候发生了一个契机，我当时编写过一个易语言软件能够自动完成大学生网课视频、作业的<a href="https://kuizuo.me/blog/chaoxing-helper">程序</a>，并将其发布在网络上免费使用。</p>
<p>挺巧的是不久后，厦门当地（距离我学校也就 5 公里）的一个工作室（算我有 4 个人）恰好看到了这个软件，问我能否在此基础上实现的一些功能，也说明了他们的目的，想要一同合作，我主要负责技术，他们负责销售推广。我思考了下可行性，于是结合转专业的懊悔之下，我义无反顾地办理休学手续，开始了我的休学之旅，准备大干一场！</p>
<p><a href="https://img.kuizuo.me/202312250425022.jpeg" target="_blank" rel="noopener noreferrer"></a></p>
<p>现在回想当时我的做法太过于任性，当时是 12 月份的时候，也就是临近考试周时，我便不在宿舍复习，甚至考试我都是直接没去的，而是直接到工作室里开始改写我的程序。以下是我当时学期的成绩单（有点惨不忍睹）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307110122716.png" alt="Untitled" class="img_OpE3"></p>
<p>我当时的想法特别天真，认为只要技术在身，天下我有。<del>甚至当时都考虑花点时间学习渗透技术来入侵教务系统来改分（好在目前为止我都没真正接触过渗透技术，不然就真的是太刑啦）</del></p>
<p>还有就是我本想的是休学带辍学，也就是休学一年期间 搞的好就辍学，搞不好就复学，当然前者是比较多的，不然也不至于连考试都没去考。（现在回想太亏了，因为这些课程缺课就相当于挂科，我还要办理重修，但没办法，当时的我可不想着以后的事情）</p>
<p>至于说为何有辍学的想法，甚至对本科证都抱着一脸嫌弃。一部分来源于技术成就的膨胀，还有一部分来自校园生活。反观学校和身边的同学，基本处于无所事事的状态（混日子），不是打游戏就是刷短视频，虽说很听规矩，要上课去上课，要开会就开会，无一缺席，日复一日，年复一年，可没点规划，就想着如何不劳而获。说真的，就这种 B 状态，谁毕业不失业？谁毕业不打工？</p>
<p><strong>什么样的环境造就什么样的人</strong>。一个地方的土壤会决定植物生长的是枝繁叶茂还是弱不禁风。至少现在我知道为何那些大厂都会优先面向 985 和 211 的学生。</p>
<p>因此我要办理休学的决心非常强烈，我父母与我姐不断给我劝告，告诉我要赚钱，要工作什么时候都来的级的以后都来得及，然而这并不是赚钱和工作的原因，而是受身边人的因素。如果要我与其同那些大学的同学这样混四年，那我不如出来实打实的干一年。（这话说的会难听，但当时我就是这么想的）父母犟不过我，我也和父母表明我肯定会拿毕业证。既然孩子有想法，那就让孩子试试。</p>
<hr>
<p>在确定好正式合作之后，我便到工作室布置好自己工位，以下是我曾经的一张正在跑项目的图</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307110122808.jpg" alt="101688581490_.pic.jpg" class="img_OpE3"></p>
<p>现在回想，当时动不动就加班到半夜，但是我对此没有怨言，甚至很享受这段时间。做自己愿意做的事情，我认为这就足够有意义了。不过这段时间持续不长，主要开发阶段也就是上半年的事情，而下半年由于本地疫情原因加上我身处老家，迟迟未能回到工位。而平常更多的任务是维护，兴致也没有一开始从 0 到 1 的热情。要知道维护是件非常枯燥且重复的事情，运营人员一有问题，就反馈到我这边，而当时写的项目又是协议项目，基本上每隔一阵子，我就要重新抓包，重新分析参数，重新部署。加上当时易语言的项目又没有 CI/CD，于是我维护完之后，就又要将易语言的项目丢到这些服务器上，我当时非常想写一个脚本，奈何我们的做单服务器基本上隔一段时间换一批，而这些服务器又是物理机（一台真实的远程机子），而非腾讯云、阿里云这种服务器，更没有批量备份，系统镜像这一功能。因此在安装环境和部署上可以说花费了我很多没必要的时间。（也可以说因此契机我彻彻底底的放弃了易语言，对于一个大型应用而言，它有数不清的缺点）</p>
<p>不过最终我还是复学了，因为在这个项目上，我只看到不断重复的日子，且项目并不是一个长久项目，在未来的某个时刻必定会被制裁（只是我没想到会这么快），最终随着一年的休学期限到来，在做了项目移交后，我便退出了该团队。在此期间自然挣了点小钱（不多，也就相当于阿里 P6 级别薪酬），足够我快活潇洒好些年了。</p>
<p>可好景不长，往往一个人在巅峰后的一段时间，必然要经历一次落差。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="复学">复学<a href="https://kuizuo.me/blog/narrate-a-college-student#%E5%A4%8D%E5%AD%A6" class="hash-link" aria-label="复学的直接链接" title="复学的直接链接">​</a></h3>
<p>办理休学手续非常容易，只需要家长知情并和学校说一下，学生提交材料，学校会给你保留学籍等待复学（期限为休学期间）。但办理复学手续可就不那么容易了，首先当时处于疫情下，学校是没那么容易进出了，需要通过学校的某个软件来申请，可由于我休学了，学院那边并没有将我的信息录入进去，导致软件压根无法证明我的身份。门口的保安可不管你进出校门做的事情，没证明就别指望进出学校。无奈，我只好联系我当时的辅导员，可保安也不认老师(真实情况就是这样)，就这样耗了 10 来分钟，保安看不下我母亲与我姐在旁边等候，才肯让我进去处理完手续。说真的，假设当天没有我家属陪同，复学恐怕就变成了辍学了。</p>
<p>进出大门的事情解决了，复学找老师签字可又没那么顺利了，具体细节我就不细说了，总之就是各种找老师签字，而有的老师今天在，有的不在，来来回回折腾了 3、4 天才把整个手续处理完，最终学是复了，书是也可继续读了，此刻的心还处在小孩子不愿上学的时代。</p>
<p>但仅仅只是一年，却正好让我赶上了学费和教案的变化。</p>
<p>休学是保留你当前的学籍，在你复学的时候，给你降级处理，将你插入到新的班级里，我原本是 2019 级的，我复学后变成了 2020 级。比较不幸的是，2020 级比 2019 级的学费还贵上 1000，我认了，毕竟这学校还有啥事情做不出来，涨点学费算什么（又不得不吐槽现在 2023 级的新生学费竟然还更贵！也确实，现在多数大学学费都上涨了）。这还不算啥，最主要是我们系的教授正好换人了，人换了也就算了，教案也跟着换，这就导致也就是我要比 19 级的学生还要多上几门课，并且有些课我还需要重新补修。妈的，什么坏事都让我遇上是吧。唉，谁叫我要休学呢。</p>
<p>而复学后学校发生的一些变数也是在我休学前未曾考虑到的，变相的让我毕业的难度增加。</p>
<div class="theme-admonition theme-admonition-info admonition_fh9h alert alert--info"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>补充</div><div class="admonitionContent_oz3Y"><p>你可能会比较好奇，为何不边上学边工作，而是要休学。我当时有两种想法，一种前面也说了，就是抱着辍学的心态，这里也就不在赘述。另一种就和我个人做事比较有关了，我做任何一件事情的前提就是不希望有其他事物来打扰，尤其是学校的因素，我学校是比较极端，规定的特别死，你想要实习只能在大四这个阶段(并且大四上还得上半个学期的课)，需要将课程修完才可。因此就有了休学。</p></div></div>
<p><strong>如果仅仅是转专业+休学的话，<code>其实</code>就没什么好看的了，不过需要这些的铺垫才有后面一言难尽的故事。</strong></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="事件之-">事件之 👮🔍💧<a href="https://kuizuo.me/blog/narrate-a-college-student#%E4%BA%8B%E4%BB%B6%E4%B9%8B-" class="hash-link" aria-label="事件之 👮🔍💧的直接链接" title="事件之 👮🔍💧的直接链接">​</a></h2>
<p>在复学的第一个学期，在 2022 年 5 月 x 号的 22 点时，我工作室负责其他业务的同事突然打电话告诉我（语气还略带急促，我猜测是因为项目与他无关就放了），说我原先待的工作室的人都被抓了，说和我之前写的项目有关，叫我把相关代码全都删除了，这段时间不要联系他们。挂断电话后，我二话不说直接把我台式电脑搬到了其他宿舍，然后把我的另一台备用笔记本电脑放在桌上，我生怕 👮‍♂️ 第二天直接来我寝室来拿电脑。就这样提心吊胆几天，我内心暗自窃喜以为没事，毕竟我已经金盆洗手不做了，应该不会再追究吧，果不其然，越担心的事越会发生。就在几天后的周六中午突然来了一通电话，我一看 0xxxxx110 显示 xxx 公安局，完了看来是逃不掉了，但我还是抱着侥幸的心理，我选择了拒接就当我睡觉没看见（我当时确实因为这个电话而醒）。打了两通后没回应，又过了一段时间后，突然我姐给我打了电话，说我学校当地的派出所打电话给她，说联系不上我，然后叫我姐联系我看能不能联系上，顺便问我发生了什么事，我并没有告诉他，然而我心里很清楚，就是和我原本工作室的同伴有关。（至于说为什么 👮‍♂️ 有我姐的联系方式，我只能说我傻逼，每次学校填写什么表格的时候，有个家长联系人，我都是填写我姐的电话。也就是从这一阶段开始，所有要我填报的信息都是虚的）</p>
<p>然后我就回了公安局一个电话，和我确认完身份后，叫我带上 🆔&nbsp; 和 📱 叫我到学校门口，有 🚓&nbsp; 接<del>送</del>。我一上车后，就直接夺走了我的 📱（真），在车上就问我：”知道我们找你来是干什么的吗？“ 我默不作声，一直到了公安局后，接下来的安排懂的都懂，从中午做 📝 一直到晚上 8 点，从醒来到回校的期间别说吃东西了，连 💧 都 tm 没得喝。这一整个阶段就是要我交代当时写这个项目的负责了什么、盈利了多少、收款方式有什么，然后打开我手机的支付宝，微信，银行卡查我账户里的 💰，总之先把我号里的 💰 转到他们的卡里先，我当时和他们说微信里的 💰 我父母给我的生活费，也是我平常主要的消费方式，我也把相关的账单记录给他们看，确信后才没有将我微信的 💰 转过去。（这么一说似乎还挺良心）</p>
<p>然后 📝&nbsp; 做完之后，👮‍♂️ 说他们局长本来应该把我带到他们当地公安局一趟，可由于疫情的因素，就为我进行取保候审，（当然，这句话肯定是有吓唬成分），接着身份信息采集，打印账单记录，签字就不在话下了。至此在这个案件没结束之前，我都将作为一个嫌疑人处理。整个过程弄完之后叫我过几天再来一趟，主要就是二次笔录，在进一步确认哪些是“非法所得”。恰好我回去的时候还下起来大雨，还得用身体去遮挡着 👮‍♂️ 给我的那几份回执单，通知书。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307110122809.jpeg" alt="Untitled" class="img_OpE3"></p>
<div class="theme-admonition theme-admonition-note admonition_fh9h alert alert--secondary"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>备注</div><div class="admonitionContent_oz3Y"><p>你可能会好奇 👮‍♂️ 为何没有没收我的”作案工具“，首先我很明确的一点是我已经半年没碰这个项目了，其次我的“同伙”已经做好了相关笔录，也把大致的流程交代完毕，那么 👮‍♂️ 大概是已经掌握我做的部分了，再者我不像我的”同伙“第一时间被“逮捕”，距离第一时间已经过去了几天，我该处理的数据也都处理的差不多了，这时候再没收的意义也不大，得不到实质信息。</p></div></div>
<p>这也是我第一次体验到被任人处置的感觉，很不是滋味，这与学生时代被老师批评罚站或者被家长训斥不同，讲不清楚的感觉。在这期间，到目前现在印象还非常深刻的一句话：<strong>不如实交代的话，让你连书都读不了！</strong></p>
<p>不过现在回想都是吓唬话，一种侦查的手段，只为了不择目的的获取更多的信息。因为 👮‍♂️ 从头到尾都没有直接通知我校，而是直接联系我本人，假设人家真的想让我读不了，直接联系学校，说明这个学生的行为，那么我必定会被开除（用开除学籍来保留学校的声誉）。不过最终学校还是知道了这件事，放到后面再说。</p>
<p>回到宿舍我便将台式搬回到原本的宿舍，坐在椅子上沉思了许久，殊不知我已经一天没吃饭了，然而此时的我毫无饿意，复杂的心绪让我发了条朋友圈：</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307110122810.png" alt="Untitled" class="img_OpE3"></p>
<p>至此，事实告诉我白白浪费了一整年的时间，并且此后还将给我带来了诸多麻烦。</p>
<p>但如今我回想这个事件，我都已经退出这个团伙近半年了，已经金盆洗手了，却依旧有所关联。👮‍♂️ 可不管你的解释，只要你参与了，获利了，势必要追究下来。如果我当时能够劝阻他们不做，或许如今就没有这么多麻烦事，可这仅存在于如果。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307110122812.png" alt="Untitled" class="img_OpE3"></p>
<p>回到这个项目而言，本质上确实不是很正规很传统，但也不至于黑产那种，更多的称呼是灰色产业。👮‍♂️ 给定性为 “<strong>提供侵入、非法控制计算机信息系统程序、工具罪”</strong>，但事实上这个程序并没有非法侵入服务器，我只是将用户的正常操作转变为电脑程序操作，将正常的数据包给模拟出来，可以省去人为操作的一个过程，而不是入侵对方的服务器，通过数据库的方式直接修改。在笔录期间，我还特意举例了游戏外挂和游戏脚本的区别，前者是实实在在的篡改数据，后者则是程序来模拟人为的情况，而我所做的部分就是游戏脚本的部分，根本不能算作非法控制。但 👮‍♂️ 不会按照我的理解，更别说检察院了，他们都只会认为这是在”破坏公平性“，那么就归属同一“恶劣”性质，就可以归属这个罪。总之我说的再多，说的再好，也都不及别人的一句反对。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="时隔一年">时隔一年<a href="https://kuizuo.me/blog/narrate-a-college-student#%E6%97%B6%E9%9A%94%E4%B8%80%E5%B9%B4" class="hash-link" aria-label="时隔一年的直接链接" title="时隔一年的直接链接">​</a></h3>
<p>这件事情过去快一年后，也就是取保时间要到之际（前段时间），👮‍♂️ 传唤几个当事人去他们当地的派出所一趟。对我而言，这段期间正好在读书阶段，此次之行大概率短时间内是回不来的，无奈之举，只能把自身的情况和辅导员说明清楚后，便踏上了不知用何形容词修饰的路。</p>
<p>在外地待了整整两周，住了整个两周的酒店。至于说过程也没啥特别的，<strong>一切都以流程为主</strong>，再次用 📝&nbsp; 核实了一下情况，从公安阶段移交到检察院阶段（此时一般就没 👮‍♂️ 的事情了），将材料都归递到检察院，等待公安这边解除取保状态(钱保)，再到检察院这边开具监视居住书，然后告诉我们可以回去等通知了。</p>
<p>所以情况就是这么个情况，抛开公安这边，回到学校的第一时间我就与辅导员汇报了情况，此时就不得不向学院解释我的情况了。</p>
<p>事实上学校是知道我这个事情的，因为当时当地的公安局的一位 👮‍♂️ 在与外地的 👮‍♂️ 配合，而这个当地的 👮‍♂️ 正好和我校学安处的老师比较熟，没过多久就找我谈了话喝 🍵（真），我将情况和他们汇报，但当时认为还没审判阶段，还只是嫌疑状态，所以就暂时不做处理。</p>
<p>直到了这次传唤之后，等我再次回到学校后，没过几天就被辅导员叫去和院长谈话了，在这谈话期间，我都一五一十的交代清楚，但听完我的叙述后，院长给我的态度其实并不友好。因为在他眼里可能也认为我编写的程序也不至于被公安传唤，再到检察院的阶段，便觉得我没有如实交代，说话的语气都带有警告之意，总之各种吓唬的口吻，院长也和 👮‍♂️ 说了几乎同样的话：“如果被我们查到事实和你所说的不符，那么我们这边会直接开除你”。最搞笑的是他还提了一下，如果被开除的话，也不要想不开什么的。就让我很无语，到底是安慰呢，还是警告。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202312262235233.png" alt="441703600436_.pic" class="img_OpE3"></p>
<p>至于为何要这么说，我想有很大原因是因为学生的负面因素会影响学校的名誉（挣钱），我想读过大学的人应该很清楚。但在谈话的期间，我表明了我是在休学期间做的事情，只是在校读书期间被叫去了（在校期间都没接触这项目），并且 👮‍♂️ 是没有直接通过校方来找我（我想是 👮‍♂️ 也不想麻烦学校），可以这么说，如果当地的 👮‍♂️ 与我校老师没有交识，校方可能都不知道我这个情况。但无论我如何解释，校方还是会以你是这个学校的学生身份来对你进行处理。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307110122813.png" alt="Untitled" class="img_OpE3"></p>
<p>当然，我可以比较肯定的是，辅导员也不希望我出事，毕竟现在大学生如果出事，辅导员也是要背点锅的，但院长的话我就不清楚了，但从与他的谈话中，总感觉他不见得我好的样子。</p>
<p>不过要说学生身份是否对我有利的话，那答案是肯定有的。比如说开具学生在校证明，酌情处理等等。但不是说大学生就是免死金牌，这要是换成未成年人，那还说不准真是。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="我对此事件的看法">我对此事件的看法<a href="https://kuizuo.me/blog/narrate-a-college-student#%E6%88%91%E5%AF%B9%E6%AD%A4%E4%BA%8B%E4%BB%B6%E7%9A%84%E7%9C%8B%E6%B3%95" class="hash-link" aria-label="我对此事件的看法的直接链接" title="我对此事件的看法的直接链接">​</a></h2>
<p>我想大致的剧情就交代了差不多，至于这一年期间找律师询问，找关系，操作什么的，我就不细说了，篇幅来的太长，且谈话内容很复杂，现实很残酷。我不想将这些负面，非正能量的东西传递下去，我本还是相信会社会会变得越来越善良的。但惟有一点我明白的是，没有办不成的事，只有到不到位是事。自我领悟这段话的意思。</p>
<p>回到这件事情，在来说说我的看法（补充）。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="利益">利益<a href="https://kuizuo.me/blog/narrate-a-college-student#%E5%88%A9%E7%9B%8A" class="hash-link" aria-label="利益的直接链接" title="利益的直接链接">​</a></h3>
<p>因为当时这个项目的收益都不是以公司的名义，而是通过多个下级使用私下转账到某个总账号（个人号），如果金额不是很大，那大概率也没什么事情，但金额一旦大，又没有缴税，这种做法无法就是非法经营。只不过项目涉及网络因素，且又有点灰产的性质，所以嘛。。。</p>
<p>从一开始这个收入形式就已经决定了 💰&nbsp; 的合法性，再加之利益摆在那，自然而然就被盯上。假设这个项目都没产生任何利益，没有破坏他人的利益，哪怕只是个灰产项目（开源），我认为 👮‍♂️&nbsp; 不会折腾到去找你的麻烦。可以说没有利益与负面影响，就不会这么多麻烦事祸降到头上。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="不一定什么都要交代">不一定什么都要交代<a href="https://kuizuo.me/blog/narrate-a-college-student#%E4%B8%8D%E4%B8%80%E5%AE%9A%E4%BB%80%E4%B9%88%E9%83%BD%E8%A6%81%E4%BA%A4%E4%BB%A3" class="hash-link" aria-label="不一定什么都要交代的直接链接" title="不一定什么都要交代的直接链接">​</a></h3>
<p>在笔录阶段，👮‍♂️ 的手段都会比较极端，希望当事人能够积极配合，透露出更多的信息，还告诉你能够酌情处理。但很多情况下，交代的越多反而不是什么好处，尤其在诱导性提问会让你偏向本不是你本意的回答。举个例子（算是真实例子，我听别人说的）：</p>
<p>某人 A 入职一个普通公司，就当 A 做了半年员工时，突然有一天警察突击这个公司，叫所有人双手抱头蹲下，A 不知道发送了什么，到了做笔录的时候，👮‍♂️&nbsp; 告诉 A ，这家公司是一家诈骗公司，这时 A 开始思考，在一开始入职的时候确实不知道是诈骗公司，但 A 做了有半年的事情，慢慢也<strong>意识</strong>到自己做的就是诈骗相关的，但待遇不错，他也不当回事，也没有离职退出什么的。接着 👮‍♂️ 问他，你知不知道你做的是诈骗，这里假设 A 有几种说法：</p>
<ol>
<li>A 是个老实人回答道直接回答道：我知道。</li>
<li>A 心里知道，且在与 👮‍♂️ 多次回答中交代自己的行为在他的认知下不是诈骗。</li>
<li>A 心里知道，可 A 嘴比较硬，死活不承认，甚至连诈骗字样都没说，一副装死的样子。且有一段这么回答：什么？这是诈骗？你要说是诈骗的话我肯定不干，谁会去干这东西。</li>
</ol>
<p>假设你是 👮‍♂️ 你会抓谁，毫无疑问，1、2 是肯定要抓的，因为从回答和行为上都已经承认了是诈骗，哪怕只是一点都算。即便 A 想要翻供，可警察却说以第一次笔录上说准，说 A 当时已经都回答了，可 A 不知要如何反驳，甚至无法反驳。</p>
<p>但 3 的情况也许就不一定了，因为从 A 的笔录证词上，确实表明 A 主观不认为是诈骗，且是员工身份，雇佣关系，即便事实发生，只要诈骗的 💰A 没拿多少，而是工资形式，除非 👮‍♂️ 真的有 A 的诈骗实质性证据，那么 A 大概率是无事发生。</p>
<p>例子可能不是很好懂，但我想要表达的是：不要被 👮‍♂️ 的话语所带入，一切回答都要以不是、不知道为主，且不要模棱两可、含糊不清，这种回答在 👮‍♂️ 认为就是”肯定“，意味着是知法犯法。一定把自己装的什么都不懂，哪怕自己内心什么都知道，也要装成哑巴一般。</p>
<p>这就不禁让我想说出但不记得是从哪里看到的一句话: <strong>坦白从宽，牢底坐穿；抗拒从严，回家过年。</strong></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="技术岗位或许是个高危职业">技术岗位或许是个高危职业<a href="https://kuizuo.me/blog/narrate-a-college-student#%E6%8A%80%E6%9C%AF%E5%B2%97%E4%BD%8D%E6%88%96%E8%AE%B8%E6%98%AF%E4%B8%AA%E9%AB%98%E5%8D%B1%E8%81%8C%E4%B8%9A" class="hash-link" aria-label="技术岗位或许是个高危职业的直接链接" title="技术岗位或许是个高危职业的直接链接">​</a></h3>
<p>试想一下，在上面的案例中，这个诈骗公司的技术人员与客服人员对比，你认为那么最后的结局会比较惨。再比如某技术人员不小心写错了代码，并且将其部署到线上环境，导致公司损失重大，你说责任在谁？</p>
<p>这样例子有太多了，很多情况下出了事情，技术人员要占据非常大的责任。但伴随这份责任的风险，也伴随着巨大的收益，风浪越大，鱼越贵。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="后悔">后悔？<a href="https://kuizuo.me/blog/narrate-a-college-student#%E5%90%8E%E6%82%94" class="hash-link" aria-label="后悔？的直接链接" title="后悔？的直接链接">​</a></h2>
<p>如果说没有这些变故，那我毫无疑问是无悔的，因为挣到了本不属于我这年龄段所拥有的资本。</p>
<p>论事实，这些资本化为乌有，同时耗费了一年的时间的情况下呢？那必然是会后悔一部分，我后悔的是没有听从我父母的劝导，在合适的时间做合适的事情，给家里人带来了许多的麻烦；而不后悔的是这段经历，让我成长了太多，让我如此膨胀的内心收敛的甚许，也让我在做任何事情都要三思而后虑，而不再一意孤行，固执己见。</p>
<p>提前发现自身的错误，及时纠正，以免重蹈覆辙。有很多道理也是在我经历之后才深刻明白的，只不过这次学习的“学费”比较贵重。</p>
<p><strong>年轻难免犯错，但也正是从错误中成长。</strong></p>
<p>故事的经历就告一段落，未来的路还很长，要考虑的事还有很多，有机会在慢慢叙述。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="一些题外话">一些题外话<a href="https://kuizuo.me/blog/narrate-a-college-student#%E4%B8%80%E4%BA%9B%E9%A2%98%E5%A4%96%E8%AF%9D" class="hash-link" aria-label="一些题外话的直接链接" title="一些题外话的直接链接">​</a></h2>
<p>我想听完上面的叙述应该能解释的通这近两个月的状态情况。</p>
<p>我本以为奖励自己搞台 MacBook 能够将状态调整回来，然而不到 1 周的时间变又开始萎靡不振，对一切新鲜事物失去了好奇感。已没有当初纯粹的兴趣，留有的是对生活的无奈。找寻不到花费一晚上解决一个 bug 的成就感，留有的只剩错中复杂的需求与枯燥乏味的工单。</p>
<p>我开始思考问题的所在，如何长期保持某种状态。最关键的因素莫过于情绪，曾经的我能够保持不断学习的状态正是因为内心无忧无虑。可时至今日，伴随我的是焦虑、压力、不安、迷茫，<strong>总觉得心里悬着什么东西放不下</strong>，可至今我也没能找到很好的解决方案，也许等案子结束，一切都将会从容自如。</p>
<p>我曾收到一本书，名为《谁的青春不迷茫》，到现在为止我也没看过，为什么呢？就书名而言，在我收到这本书时，我不认为我有何迷茫的地方。可现在我又想重新拾起这本书，却不知这本书被我放置何处。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="对我来说的本科">对我来说的本科<a href="https://kuizuo.me/blog/narrate-a-college-student#%E5%AF%B9%E6%88%91%E6%9D%A5%E8%AF%B4%E7%9A%84%E6%9C%AC%E7%A7%91" class="hash-link" aria-label="对我来说的本科的直接链接" title="对我来说的本科的直接链接">​</a></h3>
<p>在对这个社会没有认知的前提下（即休学前），我自认为能力是完全能够胜过一本的证书。外界都说没有本科不好找工作，现如今怕是本科都不好找工作咯。就我事实而说，我的几份工作都不是本科给我的，而是我的项目经验，所以也因此我更加坚信我自己对本科证的蔑视看法。</p>
<p>但有两段经历(谈话)，改变了我对此的看法。</p>
<ol>
<li>
<p>在我与一位 40 岁的技术人询问过一些问题，其中一个问题是问国内读研还有必要吗，人家没有直接否定，而是直接告诉我，很多公司现在阅读简历会更偏重于看第一学历(本科的学历)，也就是看你本科学校好不好再看你其他学历，至于为何想必不用多说，高考考进清华和研究生考进清华的难度便知。</p>
<p>而另一个问题就是问他本科有没有必要，而就举了他曾经项目投资的例子，说如果投资人发现要投资的项目的负责人没有本科学历，人家可能就不会投资了，大致意思是这样。</p>
</li>
<li>
<p>一位国外华人的 hr，看到我写的一个作品，并且他公司正好急招一名相关的开发者（远程开发），便于我交谈了起来，然而他没想到的是我竟还未毕业，而他们招聘的最低要求便是本科生。然后又进行了一番简单交谈下，得出的结论是：以我目前的身份只能开实习生或高中学历的薪资（薪资差距在 4 倍左右，国外的待遇我不说具体的薪资你大概也能猜出有多少），hr 给的建议是对我来说就是拿着点薪资就有点浪费时间，于是便错过了这个 offer。</p>
</li>
</ol>
<p>我觉得没有什么能比收益更具有说服力，因此也确确实实改变了我的看法，至少在中国是这样的。</p>
<p>就单从社会展示的数据上也可以知道，自考成人本科的人是越来越多，很多公司招聘都是本科起步。没经历只看数据对我而言是意识不到其重要性的。</p>
<p>本科固然重要，可反观现在中国的大学还是用来学知识培养人才的吗？我更倾向于说是用金钱消耗时光最终换来一本证书，至于这笔交易值不值这个价，我想多数大学生的内心已经有了答案。</p>
<p>如果要说大学对我来说最大的作用的一件事，无非就是白嫖各种大学生认证优惠（如苹果的教育优惠，github 学生认证，学生票等等），确实给我省下了不少的钱。除此之外，我很难再说出第二个实质性的作用，对我而言是更多的负面作用，例如高学费、断电断网以及学校一些系列蛮不讲理的行为，这种作风会在私立大学之间不断扩大。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="感悟">感悟<a href="https://kuizuo.me/blog/narrate-a-college-student#%E6%84%9F%E6%82%9F" class="hash-link" aria-label="感悟的直接链接" title="感悟的直接链接">​</a></h3>
<p>在之前我是比较浮躁的，总想着如何一步登天，并且我与很多平常人不同，我是不走”寻常路“，不遵循“规则”的人。绝大多数人愿安安稳稳，但我可不，我宁可去冒险一番，却又不曾思考过失败的后果。</p>
<p>我很讨厌被安排，<strong>被安排的人生，活着有什么意义？</strong> 没有自己的主见想法，不断被灌输他人的思想，这便失去了我对生活的意义。不能做自己想做的事情，那真的是一件极其可悲的事！</p>
<p>但现在让我重新做一个选择我肯定和我原先的同学一样是安安稳稳（混日子）拿个毕业证，求稳已经变为了我当下一个很重要的标识，只要主观上有风险的事，哪怕利润再高，我也不会尝试一番，我经不起风险，至少在我没有绝对”保险”的前提下。</p>
<p>可如今的现状又怎么走的了宽敞，平坦的道路。也许生来就不应该平淡度过，最终要身处何处才能对的自己颠沛流离的一生。</p>
<hr>
<p>人总是不愿听闻他人的建议，尤其这个建议还是长辈给予的。可多数人总会认为自己就是最正确的，自己的做法就是最优解，过度的自信很难看清自己几斤几两。长辈的建议是用他的人生阅历换来的，而你的想法是凭空而造，不切实际。（说的便是我自己）</p>
<p>很多建议仅仅只是传达给他人，他人即便意识到有道理，但也通常不会有所改变。只有自己亲身经历过后，回想起他人的建议是多么的宝贵。所以我也渐渐不怎么再给别人安利，反而是一种浪费口舌的行为。</p>
<hr>
<p>曾经我是一个特别念旧的人，时常会怀念过去的巅峰、美好。因为在落魄之时，也能想起曾经所拥有的，让我重拾信心。能让自己不断前行的事物只有自己所经历的过去，而记录便成了我仅有回忆方式。</p>
<p>可现实却是不断打击我曾有的美好，好像上天就巴不得我好的样子。可没有什么上帝，任何事物所发展的结果都将有始有终，自己做的事情没有人比自己清楚，我曾怪罪自己运气不好，现在我意识到是努力不足。没有不幸之人，只有懒惰之徒。在绝对的实力面前，运气再差的非酋都能化身变为欧皇。</p>
<p>曾经有个人告诉我，凡事都要往前看，<strong>只迷恋于过去只会让自己活得越加困难</strong>。可当时的我怎么可能懂得这个道理，活在过去的美好之中，忘却了当下的目标。</p>
<p>不知道自己当下要做什么，是因为不知道未来要干什么，没有目标的未来，犹如没有罗盘的航行，在茫茫大海之中无处漂泊。</p>
<p>借阿甘正传的一句名言：</p>
<blockquote>
<p><em>You got to put the past behind before you can move on.</em>(你得把过去抛在脑后才能向前看。)</p>
</blockquote>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="四年的编程">四年的编程<a href="https://kuizuo.me/blog/narrate-a-college-student#%E5%9B%9B%E5%B9%B4%E7%9A%84%E7%BC%96%E7%A8%8B" class="hash-link" aria-label="四年的编程的直接链接" title="四年的编程的直接链接">​</a></h2>
<div class="language-jsx codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-jsx codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> timeline </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token string" style="color:hsl(119, 34%, 47%)">'易语言'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'逆向'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'JavaScript/TypeScript'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'Web 全栈'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">timeline</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">forEach</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token parameter">time</span><span class="token plain"> </span><span class="token arrow operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token console class-name" style="color:hsl(35, 99%, 36%)">console</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token method function property-access" style="color:hsl(221, 87%, 60%)">log</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">time</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>回到技术角度，随着我的 4 年编程经历就这么过去了，和曾所预期的技术要求还差之甚远，没能给自己一个很好的技术交代，没能在这四年结束之际进行编码，没能给这段经历一个完美的句号。</p>
<p>倘若没有编程，我甚至都不知道自己应当从事何种职业，可以说编程是我的再生父母，没有 4 年前那场偶遇，便没有如今的我。</p>
<p><strong>道阻且长，行则将至；行而不辍，未来可期。</strong></p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>经历</category>
            <category>人生感悟</category>
        </item>
        <item>
            <title><![CDATA[MacBook 体验有感]]></title>
            <link>https://kuizuo.me/blog/macbook-experience</link>
            <guid>https://kuizuo.me/blog/macbook-experience</guid>
            <pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[作者是一位从来没用过苹果产品的程序员，但在使用了一周的 MacBook Pro 14 寸后，便爱不释手。]]></description>
            <content:encoded><![CDATA[<p>首先我不是 iphone 用户，甚至是果黑（苹果的小黑子，合理来说是苹果手机的小黑子），所以我一向从内心就很摈弃苹果的产品。因此我从来没体验过 MacOS 系统，用了近 4 年 window，不过由于我的那台 window 本 （21 年小新 pro14） 给我的体验非常差，虽然说续航勉勉强强足够支撑我一个下午的开发，但 intel 的 i5 cpu 我就没打算将其作为主力机开发（根本做不了），更多是使用向日葵远程桌面软件来远程连接到我的台式电脑，远程操控来进行开发。然而由于屏幕分辨率不同以及网络延迟，这样的体验长期下去必然会崩溃。因此<strong>更换自己的移动办公设备已经成了我当下的刚需。</strong></p>
<p>见识到诸多程序员大神都将 mac 作为主力开发机器，同时又被安利过很多次 MacBook，但我一直对 macOS 保持观察的态度，自己从未亲自体验过，最多也就看别人用用，在 window 上这些同样也能实现，何必要多此一举再去了解一个新的系统，新的操作逻辑。但直到我真正接触并体验过 macOS 后，我便爱不释手。</p>
<p>在写这篇文章时，我已经用了近一周的 macbook，因此想分享个人的购买流程、选购建议、使用感悟，或许对于某些想要尝试 MacOS 但又保有迟疑态度的用户有所帮助，也算是给曾经的我对苹果的偏见的转变。</p>
<blockquote>
<p>拖更了近两个月的博文了，摆了一整子，说来也确实有些许惭愧。不过目前生产力工具已就绪，也要开始步入正轨。</p>
</blockquote>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="购买流程">购买流程<a href="https://kuizuo.me/blog/macbook-experience#%E8%B4%AD%E4%B9%B0%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="购买流程的直接链接" title="购买流程的直接链接">​</a></h2>
<p>我是在线下 apple 授权店买的，五一假期的前一天晚上逛商场的时候正好经过，于是进去与店员交谈了一整子，又思考了一晚上，最终决定第二天直接现货 购买了丐版（标准版）的 macbook m2 pro 14 寸 16g + 512g。</p>
<p>至于选择 14 寸还是 16 寸，就因人（钱）而异，去线下给我最大的感受就是 16 寸是真的大，且厚重（14 寸也挺重的有 1.6kg），通常我在室内我就会拓展外接显示器加上偶尔有床上办公的需求所以在看到第一眼后便毫不犹豫的选择 14 寸作为我的目标尺寸。</p>
<p>至于说选择 m2 pro 还是 m2 max，这条<a href="https://www.apple.com.cn/macbook-pro-14-and-16/" target="_blank" rel="noopener noreferrer">链接</a>与下图能告诉你答案。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202305050428893.png" alt="Untitled" class="img_OpE3"></p>
<p>其次就是选配方面，在之前我是打算购买 32g + 1t 的。但在如今一堆 electron 应用（一个就要吃至少 100m 的内存），加上我本身的会有多开几个 vscode 以及多个浏览器标签， 16g 内存在 window 下对于开发从事者而言已经不够使用了，在 mac 上 从我的事实也证明 16g 内存 在两个正在运行中的 node 与 的 10 来个浏览器标签，加上一些常用软件（微信、QQ、飞书、）是有些不够用了，以下对应的活动监视图（window 中的任务管理器）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202305050429190.png" alt="Untitled" class="img_OpE3"></p>
<p>虽说有 swap，表面上的 16g 物理内存实际上运行内存可能会更多，但最主要还是看内存压力。不过即使是这样，系统也没有出现过任何的卡顿，这要是换 windows，恐怕已经蓝屏了。等哪天内存压力变红时或者出现卡顿现象，再来汇报相关进程。（新买的机子，不舍得压力测试折腾她）</p>
<p>而 m2 pro 的 512g 相比 1t 读写速度减少一半（看下图你便懂了，单通道的速度和两个 512g 组双通道相比），事实上在之前的丐版都存在这种问题，可以说苹果是巴不得你加购硬盘容量，不然硬盘速度缩半。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202305050428895.png" alt="Untitled" class="img_OpE3"></p>
<p>而 1t 及以上容量自然是无该问题，何况不加配的 mac 能叫 mac 吗。我其实是很想加配的，但无奈无现货，并且官网定制这个配置（m2 pro 32g + 1t）的还需要等待 2 个星期，据店员说“这只是预计，实际可能会更久“，不过这里不排除店员这么说诱导我在购买丐版现货。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202305050428896.jpeg" alt="Untitled" class="img_OpE3"></p>
<p>不过最终能让我购买的很大原因是在五一期间我实在不想用那破 window 本，因此第二天再次联系店员决定直接付款拿丐版现货。因为考虑两年后大概率也会更换电子设备（距离我上次更换电子设备也过去两年），所以综合考虑当前的配置在这两年应该是足以使用（这句话也许说的有点早）。</p>
<p>实体店与线上购买没有本质的区别，<strong>价格上都是一致的</strong>，也是有教育优惠的。只是人家会帮你激活设备，在你的眼皮子底下看看有无问题，确认无误后，交钱走人便可。此外可能还会赠送一些额外的一些配件，如键盘膜，屏幕膜，清洁套装，拓展坞（没有手提包），不过这些对我来说都非刚需，只要 MacBook Pro 没问题即可。</p>
<p>不过这里想要提一下，人家是挺极力推荐我购买 apple care 的（顺带有个配件券），据我了解，貌似是有一个 apple care 的指标，需要达到多少这样。不过我个人不喜欢买保险，因此便没有购买。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="刚到手的-mac-该如何处理">刚到手的 Mac 该如何处理<a href="https://kuizuo.me/blog/macbook-experience#%E5%88%9A%E5%88%B0%E6%89%8B%E7%9A%84-mac-%E8%AF%A5%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86" class="hash-link" aria-label="刚到手的 Mac 该如何处理的直接链接" title="刚到手的 Mac 该如何处理的直接链接">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="不要贴膜">不要贴膜<a href="https://kuizuo.me/blog/macbook-experience#%E4%B8%8D%E8%A6%81%E8%B4%B4%E8%86%9C" class="hash-link" aria-label="不要贴膜的直接链接" title="不要贴膜的直接链接">​</a></h3>
<p><strong>不要贴膜，不要贴膜，不要贴膜!</strong></p>
<p>我本是不想贴膜，喜欢裸机的感觉，但由于附送一个屏幕膜，我便勉为其难的贴一下，然而当我贴完后我随即将辛辛苦苦贴好的膜又给撕了下来了，并不是因为我贴的不好，而是<strong>贴膜简直就是负提升</strong>，前后对比是肉眼可见明显，这里我用相机拍不出肉眼那种效果，如果说喝枸杞是养生，那看 macbook 屏幕说是养眼可一点不为过。毕竟维修一个 Macbook 屏幕就需要 5000 左右，让我萌生一丝购买 apple care 的想法。</p>
<p>其次就是网上都有流传 macbook 的 B 面(屏幕)与 C 面(键盘)之间的间隔特别薄，贴屏幕膜或键盘膜可能都会让这层素质极高的屏幕受到一些损害。还有贴屏幕膜后，在下一次更换屏幕膜的时候，可能会导致屏幕涂层脱落，而贴键盘膜的话，时间久了会导致合盖的时候键盘膜印在屏幕上。总之，基本都是建议裸机。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="熟悉-mac-操作以及相关软件">熟悉 mac 操作以及相关软件<a href="https://kuizuo.me/blog/macbook-experience#%E7%86%9F%E6%82%89-mac-%E6%93%8D%E4%BD%9C%E4%BB%A5%E5%8F%8A%E7%9B%B8%E5%85%B3%E8%BD%AF%E4%BB%B6" class="hash-link" aria-label="熟悉 mac 操作以及相关软件的直接链接" title="熟悉 mac 操作以及相关软件的直接链接">​</a></h3>
<p>这里我推荐自己我自己看过的几个 mac 相关指南，能够帮助小白速度上手 mac。</p>
<p><a href="https://space.bilibili.com/41062266" target="_blank" rel="noopener noreferrer">Mac 云课堂的个人空间_哔哩哔哩_bilibili</a></p>
<p><a href="https://www.bilibili.com/video/BV1PF411E7LG/" target="_blank" rel="noopener noreferrer">【看完秒懂】Mac 苹果电脑超详细上手入门指南！建议做笔记！up 良心制作，用一集视频包你熟练上手 Mac_哔哩哔哩_bilibili</a></p>
<p>下面这个会比较针对与程序开发</p>
<p><a href="https://juejin.cn/post/7181274704659873850" target="_blank" rel="noopener noreferrer">2022 我用 MacBook Pro 整一年 【感想 与 踩坑指南】 - 掘金 (juejin.cn)</a></p>
<p>由于我使用时间较短，因此软件方面我不好做出评价与推荐，这里我只附上一张我已安装应用截图</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202305050428897.png" alt="Untitled" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="熟悉触控板与应用全屏提升效率">熟悉触控板与应用全屏，提升效率<a href="https://kuizuo.me/blog/macbook-experience#%E7%86%9F%E6%82%89%E8%A7%A6%E6%8E%A7%E6%9D%BF%E4%B8%8E%E5%BA%94%E7%94%A8%E5%85%A8%E5%B1%8F%E6%8F%90%E5%8D%87%E6%95%88%E7%8E%87" class="hash-link" aria-label="熟悉触控板与应用全屏，提升效率的直接链接" title="熟悉触控板与应用全屏，提升效率的直接链接">​</a></h3>
<p>如果要说使用的这段期间对笔记本电脑的体验变化无意有两点，一是颠覆我对笔记本触控板难用的想法，二是应用全屏（配合台前调度）却能够有如此高效的体验。</p>
<p>这一部分我认为不必过多吹捧，亲自到线下实体店感受才最为真实，相信我，你会爱上触摸板了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="养成保养电池的习惯">养成保养电池的习惯<a href="https://kuizuo.me/blog/macbook-experience#%E5%85%BB%E6%88%90%E4%BF%9D%E5%85%BB%E7%94%B5%E6%B1%A0%E7%9A%84%E4%B9%A0%E6%83%AF" class="hash-link" aria-label="养成保养电池的习惯的直接链接" title="养成保养电池的习惯的直接链接">​</a></h3>
<p>以下内容为使用 8 个月后的补充。</p>
<p>由于内存的不足加上重度开发，现今我的电池容量已达到 93% 了，说实话是有点小心痛的😭。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0114155525-202401141555937.png" alt="" class="img_OpE3"></p>
<p>这与我一些不好的使用习惯还是有关的，我通常会打开好几个标签页，好几个 应用而不关闭，这就导致内存时常处于高负载状态下，不断的从硬盘中拿内存，这样不仅伤硬盘同时也伤电池。此外我通常会用 Type-c 一线连接外接显示器（既可以充电又可以显示），加上我没有拔电以及关机的习惯(可能要过好几周才会重关一次)，这就导致 8 个月的时间内电池最大容量缩减。</p>
<p>现在来看或许当初买个 apple care 还能免费换个电池似乎还划得来。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="选-windows-还是-macos-">选 windows 还是 macOS ？<a href="https://kuizuo.me/blog/macbook-experience#%E9%80%89-windows-%E8%BF%98%E6%98%AF-macos-" class="hash-link" aria-label="选 windows 还是 macOS ？的直接链接" title="选 windows 还是 macOS ？的直接链接">​</a></h2>
<p>现在可以毫不犹豫的说，我会选择 macOS，下一台笔电也会选择 macOS 系统。但并不是说什么场景，macOS 都是最优选，就比如说游戏需求，我想没人会买台 mac 来作为自己的游戏机。mac 上几乎玩不到什么 3A 大作，甚至在 m2 芯片上，你可能都无法下载 wegame 来玩上一把英雄联盟。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202305050428898.png" alt="Untitled" class="img_OpE3"></p>
<p>如果你有桌面游戏的需求，建议拉黑 mac。此外还有一些 window 的专业软件，你在 mac 上可能找不到与之对应或平替的软件，尤其是在大学课程中，老师几乎不可能给学生发个 dmg 文件，如果你在大学期间买 mac，又要兼顾学校的课程软件需求，又不得不安装 window 虚拟机，与其如此折腾不如一开始就选用相对便宜的 win 本，还能减少一些经济压力。不过我觉得大学老师上课所说的一些软件都没必要安装，反而占用一些不必要的空间，（vc++、eclipse 等等），如果你们老师提到了 Vscode 那当我没说。</p>
<p>但出色的系统、高素质的屏幕，注定能让 MacBook 能够成为某部分群体的生产力工具，挣钱的机器。选用 macbook 的用户想必都希望在它任职期间产生数十倍的价值，当然排除我这个买来尝鲜的。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="开发上的体验提升">开发上的体验提升<a href="https://kuizuo.me/blog/macbook-experience#%E5%BC%80%E5%8F%91%E4%B8%8A%E7%9A%84%E4%BD%93%E9%AA%8C%E6%8F%90%E5%8D%87" class="hash-link" aria-label="开发上的体验提升的直接链接" title="开发上的体验提升的直接链接">​</a></h2>
<p>目前手头的三台电脑设备对应的 CPU（性能从高到低）M2 Pro &gt; AMD 5900x &gt; i5-11300H</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202305050428899.png" alt="Untitled" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202305050428900.png" alt="Untitled" class="img_OpE3"></p>
<p>这里我没找到比较好的前端 benchmark 项目，但就从我个人直观的体验与在这三台机器启动同一个前端项目启动打包来看，在冷启动上，m2 pro 耗费 1.7s, 5900x 耗费 2.8s，i5-11300H 我都不想拉项目，去年的暑假靠这台 win 本进行开发，别提体验有多差，每次都需要干巴巴的干等项目完全启动就需要等个 2、3 分钟（不夸张），有时候可能因为某些特殊原因需要重启服务，好的，又浪费个 2、3 分钟。影响你效率的可能不只是环境，还有你的机器。</p>
<p>冷启动都能有近 1s 的优势，就别提热加载和打包速度上，这里直接给出我打包一个 Nuxt 项目的打包时间输出耗时，m2 pro 耗时 27s、5900x 耗时 116s（数据真实有效），快的让我有点感觉我是不是少写了某部分代码，还是说多注释了些代码。</p>
<p>性能优势可能不是最大的优势，但编程环境上 Mac 绝对比 Window 来的好，一个 <a href="https://brew.sh/" target="_blank" rel="noopener noreferrer">Homebrew</a> 就已经能解决百分之 90 的编程语言环境，而这换到 Window 上则有诸多的安装方式。至少你不必像 Window 那样还需要打开设置面板配置环境变量。而 MacOS 与 Linux 又非常相似，都可以在命令行中运行 Unix、bash/zsh、以及其他 shell 命令. 所以至少从 <code>代码</code> 开发方面, Mac 绝对比 Window 来的好，这也是多数开发人员选择 Mac 的原因。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="结语">结语<a href="https://kuizuo.me/blog/macbook-experience#%E7%BB%93%E8%AF%AD" class="hash-link" aria-label="结语的直接链接" title="结语的直接链接">​</a></h2>
<p>相对遗憾的是购买的还是相对匆忙，就是没有加配 32g，虽说目前来说 16g 勉强能够应付绝大部分场景，但免不了后续爆内存，又无能为力的情况。但想到自己仿佛挖到了一个新世界的宝藏，这种担忧就显得不足为惧。</p>
<p>在写完这篇稿子时，回头用起 win 时，都习惯性的按下 <code>Alt + C</code> 键位，殊不知 <code>Ctrl + C</code> 才是 win 的复制。适应也许只需要几天的时间，但回去也许可能大半辈子都不再回去。</p>
<p>从被别人安利到用 mac，再到自己安利别人用 mac，这种对 macOS 系统相见恨晚的感受，也许只有使用过 macOS 的人才能理解。<strong>很多东西只有自己用过才知道，只有尝试过，才知道适不适合自己。不尝试并不会丢失什么，但尝试过后往往能够收获意想不到的东西。</strong></p>
<p>如果你还没有尝试过 macOS 系统，那么你或许真的错过了很多。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>macOS</category>
            <category>MacBook</category>
            <category>记录</category>
            <category>使用体验</category>
        </item>
        <item>
            <title><![CDATA[距离我上篇笔记还是在?]]></title>
            <link>https://kuizuo.me/blog/why-i-dont-write-notes</link>
            <guid>https://kuizuo.me/blog/why-i-dont-write-notes</guid>
            <pubDate>Mon, 13 Mar 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[当我起这个标题时，其实我已经很久没更新（翻看）过笔记了，甚至我都不记得我的博客还有笔记这个东西。]]></description>
            <content:encoded><![CDATA[<p>当我起这个标题时，其实我已经很久没更新（翻看）过笔记了，甚至我都不记得我的博客还有笔记这个东西。</p>
<p>当我翻阅 git 记录，寻找上一次在笔记文件夹的 commit 提交记录，还是在去年的 10 月 1 号。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/U0EDw0PkAf1.png" alt="image_U0EDw0PkAf1" class="img_OpE3"></p>
<p>然而并不是我的技术栈没更新，而是我实实在在没去为这些技术栈编写过笔记。仅有的只是博文来记录自己所用的过程。</p>
<p>因此我想思考下我为何不记录笔记。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="笔记的意义">笔记的意义<a href="https://kuizuo.me/blog/why-i-dont-write-notes#%E7%AC%94%E8%AE%B0%E7%9A%84%E6%84%8F%E4%B9%89" class="hash-link" aria-label="笔记的意义的直接链接" title="笔记的意义的直接链接">​</a></h2>
<p>首先不妨回答一下我所认为的笔记意义。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="检索高效">检索高效<a href="https://kuizuo.me/blog/why-i-dont-write-notes#%E6%A3%80%E7%B4%A2%E9%AB%98%E6%95%88" class="hash-link" aria-label="检索高效的直接链接" title="检索高效的直接链接">​</a></h3>
<p>当我需要回忆我曾经学习过的某个知识点时，笔记可以说是最直接有效的办法。与其再次使用搜索引擎搜寻答案，不妨直接从答案中找答案。</p>
<blockquote>
<p>正如我笔记简介所述：<strong>做到即查即用，能复制粘贴解决的，就绝不百度。</strong></p>
</blockquote>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="巩固理解">巩固理解<a href="https://kuizuo.me/blog/why-i-dont-write-notes#%E5%B7%A9%E5%9B%BA%E7%90%86%E8%A7%A3" class="hash-link" aria-label="巩固理解的直接链接" title="巩固理解的直接链接">​</a></h3>
<p>相比绝大多数笔记内容都是初学者去记录自己所学，在这个阶段，你对知识的掌握程度是比较浅显的，而笔记无非能加快你的理解，同时也是是成本最低的，翻看自己的内容远比理解他人的内容来的简单。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="学习方式">学习方式<a href="https://kuizuo.me/blog/why-i-dont-write-notes#%E5%AD%A6%E4%B9%A0%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="学习方式的直接链接" title="学习方式的直接链接">​</a></h2>
<p>很多初学者除了通过视频教程来学习，当然不乏有些人是通过别人的学习经验（即笔记）来进行学习，包括我一开始也是通过刷别人的笔记来学习某个知识。</p>
<p>不过相对于视频而言，视频通常很啰嗦且时长感人（动辄可能数天的时长）。很多人不喜欢铺垫，不喜欢废话，就想知道某个知识点怎么用，结果如何。同时视频没有搜索功能，如果视频创作者没有对视频进行分集，当你需要回看的时候时，你往往需要从数十分钟的视频中不断的拖动，甚至到最后才发现原来我看的不是这个视频。</p>
<p>而文档则不会，那你可以通过 Ctrl + F 搜索你想要的关键字，相比视频而言，检索的效率将会大大提升。</p>
<p>这也是我为何从视频学习转成文档学习很重要的原因。我也很推荐如今的学习者从文档学习，而不是视频学习。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="为何不写笔记">为何不写笔记<a href="https://kuizuo.me/blog/why-i-dont-write-notes#%E4%B8%BA%E4%BD%95%E4%B8%8D%E5%86%99%E7%AC%94%E8%AE%B0" class="hash-link" aria-label="为何不写笔记的直接链接" title="为何不写笔记的直接链接">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="笔记应该记录哪些知识点">笔记应该记录哪些知识点？<a href="https://kuizuo.me/blog/why-i-dont-write-notes#%E7%AC%94%E8%AE%B0%E5%BA%94%E8%AF%A5%E8%AE%B0%E5%BD%95%E5%93%AA%E4%BA%9B%E7%9F%A5%E8%AF%86%E7%82%B9" class="hash-link" aria-label="笔记应该记录哪些知识点？的直接链接" title="笔记应该记录哪些知识点？的直接链接">​</a></h3>
<p>笔记无疑是耗时的，你可能会花费数个小时的时间，去总结一个可能你职业生涯中都用不到几次的知识。</p>
<p>为什么这么说，因为我发现我有很多笔记就是这种情况，你也可以回想一下你所记录的笔记，有多少是经常使用到的。</p>
<p>尤其是当你使用的足够多的情况下，你甚至都无需翻阅笔记。脑海中自然就会复现出你需要的东西，此时为何还需要看笔记。为了验证脑海中的正确性吗？</p>
<p>想想看你会为怎样的知识点大费周章的记录，是一个自己都不怎么用的知识点，还是用到滚瓜烂熟的知识点？</p>
<p>当我们经常使用某些知识点时，自然而然就会记住它们，这时笔记就没有太大的意义了；而对于使用频率较低的知识点，记录下来的意义也不是很大。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="搜索大于记录">搜索大于记录<a href="https://kuizuo.me/blog/why-i-dont-write-notes#%E6%90%9C%E7%B4%A2%E5%A4%A7%E4%BA%8E%E8%AE%B0%E5%BD%95" class="hash-link" aria-label="搜索大于记录的直接链接" title="搜索大于记录的直接链接">​</a></h3>
<p>究其原因，还是因为我翻看笔记的频率变少了许多。如今的搜索引擎很智能，只要你用的不是百度，就能很快速的搜寻到答案，为何还需要记录一下，换成自己熟悉的口吻，到最后就变成上面所说的那样。而在 ChatGPT ，new bing 的诞生下，更加剧了我这一行为。</p>
<p><strong>当搜索大于记录时，笔记就显得弱不禁风。</strong></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="官方文档与学习笔记">官方文档与学习笔记<a href="https://kuizuo.me/blog/why-i-dont-write-notes#%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E4%B8%8E%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0" class="hash-link" aria-label="官方文档与学习笔记的直接链接" title="官方文档与学习笔记的直接链接">​</a></h3>
<p>说实话，当你看到别人的笔记的时候，你会认真的从头到尾看一遍吗？我想不会，更多情况我们只利用搜索功能去获取我们想要的知识点。</p>
<p>但如果此时有两份文档，一份是官方的技术文档，一份是他人的学习笔记文档。你会选择哪一份？是我肯定毫不犹豫的选择前者，他人的学习笔记并不会及时更新，但官方文档只要还在维护，那么必定处于常更新的状态。假设我照着他人的学习笔记学习，此时正好有个函数的使用方法更新了，那么我必定会踩坑，导致不必要的 bug。而官方文档则不会，官方一旦有破坏性更新，通常会有显眼的提示，和 changelog 可供我参考，这就能及时有效的帮我排除不必要的坑。</p>
<p>这也是我为什么会宁愿去看官方文档，哪怕是英文的，也绝不愿去看第三方中文翻译后的文档，我有太多的坑就是因为更新不及时，存在信息差导致的。所以无论如何，能获取一手的信息，就别用二手的，甚至你也不知道人家会加工成什么样子。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="最后">最后<a href="https://kuizuo.me/blog/why-i-dont-write-notes#%E6%9C%80%E5%90%8E" class="hash-link" aria-label="最后的直接链接" title="最后的直接链接">​</a></h2>
<p>综上，我想我已经把我为何不写笔记的原因讲述的比较清楚了。</p>
<p>当然我并不是说笔记就一无是处，因为每个人，每个阶段的学习方式不同。曾经我也是笔记学习的追崇者，但如今的我能够不借助视频教程，不借助笔记来进行独立学习，所以为何不选择一个对我而言更高效的方式。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>杂谈</category>
        </item>
        <item>
            <title><![CDATA[Typescript 全栈最值得学习的技术栈 TRPC]]></title>
            <link>https://kuizuo.me/blog/typescript-full-stack-technology-trpc</link>
            <guid>https://kuizuo.me/blog/typescript-full-stack-technology-trpc</guid>
            <pubDate>Tue, 07 Mar 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[本文介绍了 tRPC 技术以及它与传统 RESTful API 的区别。同时 tRPC 可以帮助人们更快地开发全栈 TypeScript 应用程序，同时无需传统的 API 层，并保证应用程序在快速迭代时的稳定性。]]></description>
            <content:encoded><![CDATA[<p>如果你想成为一个 <strong>Typescript 全栈工程师</strong>，那么你可能需要关注一下 <a href="https://trpc.io/" target="_blank" rel="noopener noreferrer">tRPC</a> 框架。</p>
<p>本文总共会接触到以下主要技术栈。</p>
<ul>
<li><a href="https://nextjs.org/" target="_blank" rel="noopener noreferrer" title="Next.js">Next.js</a></li>
<li><a href="https://trpc.io/" target="_blank" rel="noopener noreferrer" title="TRPC">TRPC</a></li>
<li><a href="https://www.prisma.io/" target="_blank" rel="noopener noreferrer" title="Prisma">Prisma</a></li>
<li><a href="https://github.com/vriad/zod" target="_blank" rel="noopener noreferrer" title="Zod">Zod</a></li>
<li><a href="https://authjs.dev/" target="_blank" rel="noopener noreferrer" title="Auth.js">Auth.js</a></li>
</ul>
<p>不是介绍 tRPC 吗，怎么突然出现这么多技术栈。好吧，主要这些技术栈都与 typescript 相关，并且在 trpc 的示例应用中都或多或少使用到，因此也是有必要了解一下。</p>
<p>在线体验地址：<a href="https://trpc.kuizuo.me/" target="_blank" rel="noopener noreferrer">TRPC demo</a></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="end-to-end-typesafe-apis端到端类型安全">End-to-end typesafe APIs(端到端类型安全)<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#end-to-end-typesafe-apis%E7%AB%AF%E5%88%B0%E7%AB%AF%E7%B1%BB%E5%9E%8B%E5%AE%89%E5%85%A8" class="hash-link" aria-label="End-to-end typesafe APIs(端到端类型安全)的直接链接" title="End-to-end typesafe APIs(端到端类型安全)的直接链接">​</a></h2>
<p>在介绍相关技术前，不妨思考一个问题。</p>
<blockquote>
<p>当进行网络请求和 API 调用时，你是否知道本次请求的参数类型以及返回的响应数据类型？知道了请求的数据类型与响应的数据类型，会为得到的 json 数据定义 type/interface，使其有更好的类型提示？还是会在 any 类型下获取属性，但由于没有类型提示，导致写错个单词，最终提示 Cannot read properties of undefined (reading 'xxx')？</p>
</blockquote>
<p>对于大部分前端应用而言，类型往往常被忽略的，这就导致不知道这个请求的提交参数、响应结果有什么数据字段。举个 axios 发送 post 请求的例子</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/20230308142331808.png" alt="image-20230308142331808" class="img_OpE3"></p>
<p>这是一个 post 请求用于实现登录的，但是这个响应数据 data 没有任何具体提示（这里的提示是 vscode 记录用户最近输入的提示），这时候如果一旦对象属性拼写错误，就会导致某个数据没拿到，从而诱发 bug。同理提交的请求体 body 不做约束，万一这个请求还有验证码 code 参数，但是我没写上，那请求就会失败，这是就需要通过调试输出，甚至需要抓包比对原始数据包，其过程可想而知。</p>
<p>最主要的是没有类型约束的情况下，非常容易出现访问某个对象属性不存在，js 开发者肯定经常遇到如下错误提示。</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">Cannot read properties </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">of</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">undefined</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">reading </span><span class="token string" style="color:hsl(119, 34%, 47%)">'xxx'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>有太多时候就是因为没有类型，无形间诱发 bug，这也是很多做 api 接口都常常忽视的一点。</p>
<blockquote>
<p>因此我个人所认为的未来 Web 框架形态是要满足的前提就是前后端类型统一，即可以将后端的类型无缝的给前端使用，反之同理。而像 Next、Nuxt 这样的全栈框架便是趋势所向。</p>
</blockquote>
<p>当然 axios 是可以通过泛型的方式拿到 data 的数据类型提示，就如下图所示。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/20230308142452678.png" alt="image-20230308142452678" class="img_OpE3"></p>
<p>但这样为了更好的类型提示，无形之间又增加了工作量，我需要定义每个接口的 Response 与 Body 类型，就极易造成开发疲惫，不愿维护代码。而本次所要介绍的技术栈 tRPC 就能够帮你省去重复的类型定义的一个 web 全栈框架。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="trpc"><a href="https://github.com/trpc/trpc" target="_blank" rel="noopener noreferrer">tRPC</a><a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#trpc" class="hash-link" aria-label="trpc的直接链接" title="trpc的直接链接">​</a></h2>
<p>tRPC 是一个基于 TypeScript 的远程过程调用框架，旨在简化客户端与服务端之间的通信过程，并提供高效的类型安全。它允许您使用类似本地函数调用的方式来调用远程函数，同时自动处理序列化和反序列化、错误处理和通信协议等底层细节。</p>
<p>借官方 Feature</p>
<ul>
<li>Automatic type-safety（自动类型安全）</li>
<li>Snappy DX（敏捷高效的开发者体验）</li>
<li>Is framework agnostic （不依赖于特定框架）</li>
<li>Amazing autocompletion（出色的自动补全功能）</li>
<li>Light bundle size（轻量级打包大小）</li>
</ul>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="什么时候该使用-trpc">什么时候该使用 tRPC<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E8%AF%A5%E4%BD%BF%E7%94%A8-trpc" class="hash-link" aria-label="什么时候该使用 tRPC的直接链接" title="什么时候该使用 tRPC的直接链接">​</a></h3>
<p>这个问题非常好，因为我在了解到 tRPC，并参阅了一些基本示例与实践一段时间后发现 trpc 和 http 的应用场景可以说非常相似，完全可以使用 trpc 来替代 http，只不过写法上从 <strong>发送 http 请求 ⇒ 调用本地函数</strong>（这在后面会演示到）。</p>
<p>而 trpc 又以类型安全与高效著称，如果你的 Web 应用的程序是基于 typescript，并且需要有高效的性能，那么 tRPC 就是一个很好的选择。</p>
<p>tRPC 可以作为 REST/GraphQL 的替代品，如果前端与后端共享代码的 TypeScript monorepo，trpc 则可以无需任何类型转换，也不太会有心智负担。</p>
<p><strong>请记住，tRPC 只有当您在诸如 Next、Nuxt、SvelteKit、SolidStart 等全栈项目中使用 TypeScript 时，tRPC 才会发挥其优势。</strong></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="trpc-如何进行接口调用">tRPC 如何进行接口调用<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#trpc-%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8" class="hash-link" aria-label="tRPC 如何进行接口调用的直接链接" title="tRPC 如何进行接口调用的直接链接">​</a></h2>
<video src="https://assets.trpc.io/www/v10/v10-dark-landscape.mp4" controls="" width="100%" height="auto"></video>
<p>一图胜千言，你可以点击 <a href="https://trpc.io/#try-it-out" target="_blank" rel="noopener noreferrer" title="这里">这里</a> 在线体验一下 tRPC，并且查看其目录结构，以及调用方式。下面我一步步讲解如何进行接口调用。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="定义服务端">定义服务端<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E5%AE%9A%E4%B9%89%E6%9C%8D%E5%8A%A1%E7%AB%AF" class="hash-link" aria-label="定义服务端的直接链接" title="定义服务端的直接链接">​</a></h3>
<p>这里以 Next.js 的目录结构而定。创建 <code>server/trpc.ts</code>，如下代码。分别导出 router, middleware, procedure</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>server/trpc.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> initTRPC </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@trpc/server'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> t </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> initTRPC</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">create</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> router </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> t</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">router</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> middleware </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> t</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">middleware</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> publicProcedure </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> t</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">procedure</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>创建项目(根)路由文件 <code>pages/api/trpc/[trpc].ts</code></p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>server/trpc.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">*</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">as</span><span class="token plain"> trpc </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@trpc/server'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> publicProcedure</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> router </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'./trpc'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> appRouter </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">router</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  greeting</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> publicProcedure</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">query</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hello tRPC!'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">type</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">AppRouter</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">typeof</span><span class="token plain"> appRouter</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此时已经定义好了一个路由地址 <code>api/trpc/[trpc].ts</code>（这里 endpoint(端点)会在客户端中使用到），以及 <code>greeting</code> 函数，服务端的工作就暂且完毕。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="创建客户端">创建客户端<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E5%88%9B%E5%BB%BA%E5%AE%A2%E6%88%B7%E7%AB%AF" class="hash-link" aria-label="创建客户端的直接链接" title="创建客户端的直接链接">​</a></h3>
<p>创建 <code>utils/trpc.ts</code> 文件，代码如下</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>utils/trpc.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> httpBatchLink </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@trpc/client'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> createTRPCNext </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@trpc/next'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">type</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> AppRouter </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'../pages/api/trpc/[trpc]'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">getBaseUrl</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">typeof</span><span class="token plain"> window </span><span class="token operator" style="color:hsl(221, 87%, 60%)">!==</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'undefined'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// In the browser, we return a relative URL</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">''</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// When rendering on the server, we return an absolute URL</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// reference for vercel.com</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">process</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">env</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">VERCEL_URL</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token template-string string" style="color:hsl(119, 34%, 47%)">https://</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token template-string interpolation">process</span><span class="token template-string interpolation punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token template-string interpolation">env</span><span class="token template-string interpolation punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token template-string interpolation constant" style="color:hsl(35, 99%, 36%)">VERCEL_URL</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// assume localhost</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token template-string string" style="color:hsl(119, 34%, 47%)">http://localhost:</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token template-string interpolation">process</span><span class="token template-string interpolation punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token template-string interpolation">env</span><span class="token template-string interpolation punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token template-string interpolation constant" style="color:hsl(35, 99%, 36%)">PORT</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation operator" style="color:hsl(221, 87%, 60%)">??</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation number" style="color:hsl(35, 99%, 36%)">3000</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> trpc </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:hsl(221, 87%, 60%)">createTRPCNext</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token generic-function generic class-name" style="color:hsl(35, 99%, 36%)">AppRouter</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token function" style="color:hsl(221, 87%, 60%)">config</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      links</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token function" style="color:hsl(221, 87%, 60%)">httpBatchLink</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          url</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">getBaseUrl</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">+</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'/api/trpc'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>在 <code>_app.tsx</code> 包装一下</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>_app.tsx<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">type</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> AppType </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'next/app'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> trpc </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'../utils/trpc'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> MyApp</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">AppType</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> Component</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> pageProps </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">Component </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token operator" style="color:hsl(221, 87%, 60%)">...</span><span class="token plain">pageProps</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">/</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token plain"> trpc</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">withTRPC</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">MyApp</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>有了这个对象后，我们就可以开始尽情调用服务端所定义好了函数了。</p>
<p>当你导入 trpc 并输入 <code>trpc.</code> 时，将会提示出服务端定义好的 <code>greeting</code> 函数，如下图所示。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/YDKc7TixQA.png" alt="" class="img_OpE3"></p>
<p>此时通过 <code>const result = trpc.greeting.useQuery()</code> 便可调用 <code>greeting</code> 函数，其中 <code>result.data</code> 便可拿到 <code>'hello tRPC!'</code> 信息。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="这个过程发生了什么">这个过程发生了什么？<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E8%BF%99%E4%B8%AA%E8%BF%87%E7%A8%8B%E5%8F%91%E7%94%9F%E4%BA%86%E4%BB%80%E4%B9%88" class="hash-link" aria-label="这个过程发生了什么？的直接链接" title="这个过程发生了什么？的直接链接">​</a></h3>
<blockquote>
<p>文档: <a href="https://trpc.io/docs/useQuery" target="_blank" rel="noopener noreferrer" title="useQuery() | tRPC">useQuery() | tRPC</a></p>
</blockquote>
<p>不妨此时打开控制台面板，看看请求</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/WfW8ehqUKz.png" alt="" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/qicvoGjshx.png" alt="" class="img_OpE3"></p>
<p>不难看出，调用 greeting 函数本质是向 <code>/api/trpc/greeting</code> 发送了 http 请求，并且携带参数 batch 和 input，虽然我们暂时还没有传。默认 input 为 <!-- -->。</p>
<p>要支持传递参数，首先需要在服务端定义传递参数的类型（会有 Zod 对参数效验），这样客户端才有对应的类型提示。然后调用 greeting 函数时，通过通过函数参数的形式来传递请求参数。</p>
<p>举例说明，比如说我们将 appRouter 改写成这样，通过 input 参数指定了 <code>useQuery</code> 需要传递一个 <code>name</code> 为字符串且不为空的对象。</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> z </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'zod'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> appRouter </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">router</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  greeting</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> publicProcedure</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">input</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">object</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">nullish</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">query</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> input </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        text</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token template-string string" style="color:hsl(119, 34%, 47%)">hello </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token template-string interpolation">input</span><span class="token template-string interpolation operator" style="color:hsl(221, 87%, 60%)">?.</span><span class="token template-string interpolation">name </span><span class="token template-string interpolation operator" style="color:hsl(221, 87%, 60%)">??</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation string" style="color:hsl(119, 34%, 47%)">'world'</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>调用 <code>trpc.greeting.useQuery({ name: 'kuizuo' })</code> 发送的请求的 query 参数则变为</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/20230307214659.png" alt="" class="img_OpE3"></p>
<p>不仅于此，你如果同时调用了多次 greeting 函数，如</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_">pages/index.tsx<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> result1 </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> trpc</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">greeting</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">useQuery</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'kuizuo1'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> result2 </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> trpc</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">greeting</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">useQuery</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'kuizuo2'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> result3 </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> trpc</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">greeting</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">useQuery</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'kuizuo3'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>tRPC 会将这三次函数调用合并成一次 http 请求，并且得到的响应本文也是以多条数据的形式返回</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/ufrhaugaIj.png" alt="" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/cvlDJjhwPl.png" alt="" class="img_OpE3"></p>
<p>分别输出三者 result 也没有任何问题。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/hbL8So_RzB.png" alt="" class="img_OpE3"></p>
<p>这是 tRPC 的一个特性：<strong>请求批处理，将同时发出的请求（调用）可以自动组合成一个请求。</strong></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="usemutation--trpc"><a href="https://trpc.io/docs/useMutation" target="_blank" rel="noopener noreferrer" title="useMutation() | tRPC">useMutation() | tRPC</a><a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#usemutation--trpc" class="hash-link" aria-label="usemutation--trpc的直接链接" title="usemutation--trpc的直接链接">​</a></h4>
<p>tRPC 同样也支持 post 请求，例如</p>
<p>服务端代码</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>server/trpc.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> appRouter </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">router</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  createUser</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> publicProcedure</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">input</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">object</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">string</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">mutation</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">req </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> user</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> User </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> req</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">input</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> user</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>客户端代码</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>pages/index.tsx<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">default</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">function</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">IndexPage</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> mutation </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> trpc</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">createUser</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">useMutation</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// ERROR!</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// mutation.mutate({ name: 'kuizuo' });</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">handleCreate</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    mutation</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">mutate</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'kuizuo'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">div</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">button onClick</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain">handleCreate</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> disabled</span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain">mutation</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">isLoading</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        Create</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token operator" style="color:hsl(221, 87%, 60%)">/</span><span class="token plain">button</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain">mutation</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">error </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&amp;&amp;</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">p</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain">Something went wrong</span><span class="token operator" style="color:hsl(221, 87%, 60%)">!</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain">mutation</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">error</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">message</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token operator" style="color:hsl(221, 87%, 60%)">/</span><span class="token plain">p</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token operator" style="color:hsl(221, 87%, 60%)">/</span><span class="token plain">div</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="theme-admonition theme-admonition-danger admonition_fh9h alert alert--danger"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>危险</div><div class="admonitionContent_oz3Y"><p>这里需要注意 <code>mutate</code> 方法无法在外层直接调用，否则将会提示</p><div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">Unhandled Runtime Error</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">Error</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Maximum update depth exceeded</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain"> This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain"> React limits the </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">number</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">of</span><span class="token plain"> nested updates to prevent infinite loops</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>主要防止这个组件被其他组件调用，此时自动调用 mutate 函数，导致不可控且循环调用的情况，因此需要通过一个事件（比如点击事件）来触发。</p></div></div>
<p>此时请求变为 post 请求，并且携带的参数也以 body 形式传递。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/-qEI8jR1uM.png" alt="" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/RTdWJn_55p.png" alt="" class="img_OpE3"></p>
<p>通过 useQuery 和 useMutation 就能够用 tRPC 实现最基本的 CRUD。此外还有 useInfiniteQuery 可以用作类似无限下拉查询，类似 <a href="https://swr.bootcss.com/examples/infinite-loading" target="_blank" rel="noopener noreferrer">SWR 无限加载</a>。useQueries 批量查询，使用 <a href="https://trpc.io/docs/subscriptions" target="_blank" rel="noopener noreferrer">Subscriptions</a> 进行订阅 WebSocket 等等。</p>
<p>tRPC 针对 react 项目的查询主要依赖于 <a href="https://tanstack.com/query/v4/docs/react/adapters/react-query" target="_blank" rel="noopener noreferrer" title="@tanstack/react-query">@tanstack/react-query</a>，你也可以到 <a href="https://trpc.io/docs/react-query" target="_blank" rel="noopener noreferrer" title="tRPC React Query documentation">tRPC React Query documentation</a> 查看相关 hook。</p>
<p>从上述例子中你就会发现，tRPC 将 http 请求给我们包装成了函数形式调用，即上文所说的，调用服务端接口的形式由 <strong>发送 http 请求 ⇒ 调用本地函数</strong>。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="不足">不足<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E4%B8%8D%E8%B6%B3" class="hash-link" aria-label="不足的直接链接" title="不足的直接链接">​</a></h3>
<p>不过也并非没有缺点（个人认为）。</p>
<p>首先不如传统的 RESTFUL 来的直观，假设我现在在服务端定义了一个服务，那么我只能通过<code>@trpc/client</code> 创建客户端进行调用。虽然也能用 http 的形式，但调用的很不优雅。</p>
<p>在我印象中，RPC 框架通常是可以跨语言进行调用的，比如 gRPC 框架，然而<strong>tRPC 目前只能在 Typescript 项目中进行调用</strong>，我倒是希望能向 gRPC 那个方向发展，不过不同语言间的类型安全又是个大麻烦。</p>
<p>学习成本与项目成本偏高，tRPC 对整个全栈项目的技术要求比较高，并且限定于 typescript，如果你<del>想</del>将你的项目从传统的 Restful 迁移到 tRPC 上，无疑是个工程量大，且不讨好的事。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="创建工程">创建工程<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E5%88%9B%E5%BB%BA%E5%B7%A5%E7%A8%8B" class="hash-link" aria-label="创建工程的直接链接" title="创建工程的直接链接">​</a></h2>
<p>这里选用 <a href="https://create.t3.gg/" target="_blank" rel="noopener noreferrer" title="Create T3 App">Create T3 App</a> 用于创建应用（也可以选择 <a href="https://github.com/trpc/examples-next-prisma-starter" target="_blank" rel="noopener noreferrer" title="trpc/examples-next-prisma-starter">trpc/examples-next-prisma-starter</a>），Create T3 App 集成了诸多有关 TypeScript full-stack 相关的技术栈，其中就包括了本文所要介绍的几个技术栈。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/8BUcBPK8In.png" alt="" class="img_OpE3"></p>
<div class="language-bash codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-bash codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token function" style="color:hsl(221, 87%, 60%)">pnpm</span><span class="token plain"> create t3-app@latest</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>安装过程如下</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/ERGzEt2Tq8.png" alt="" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="prisma">prisma<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#prisma" class="hash-link" aria-label="prisma的直接链接" title="prisma的直接链接">​</a></h3>
<p>此时安装完先别急着 pnpm run dev 启动项目，首先执行</p>
<div class="language-bash codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-bash codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">npx prisma db push</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>运行结果如下</p>
<div class="language-bash codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-bash codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">Environment variables loaded from .env</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">Prisma schema loaded from prisma schema.prisma</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">Datasource </span><span class="token string" style="color:hsl(119, 34%, 47%)">"db"</span><span class="token builtin class-name" style="color:hsl(35, 99%, 36%)">:</span><span class="token plain"> SQLite database </span><span class="token string" style="color:hsl(119, 34%, 47%)">"db.sqlite"</span><span class="token plain"> at </span><span class="token string" style="color:hsl(119, 34%, 47%)">"file:./db.sqlite"</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">SQLite database db.sqlite created at file:./db.sqlite</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">Your database is now </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">in</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">sync</span><span class="token plain"> with your Prisma schema. Done </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">in</span><span class="token plain"> 81ms</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>这会将数据库与 prisma 的 schema 同步，说人话就是将数据库的表与 <code>schema.prisma</code> 文件中的 model 对应。</p>
<details class="details_Nljn alert alert--info details_gt4T" data-collapsed="true"><summary>schema.prisma</summary><div><div class="collapsibleContent_eI0f"><div class="language-prisma codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_">prisma/schema.prisma<span style="flex:1;text-align:right">prisma</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-prisma codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">generator client {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    provider = "prisma-client-js"</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">datasource db {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    provider = "sqlite"</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    url      = env("DATABASE_URL")</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">model Example {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    id        String   @id @default(cuid())</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    createdAt DateTime @default(now())</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    updatedAt DateTime @updatedAt</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">// Necessary for Next auth</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">model Account {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    id                String  @id @default(cuid())</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    userId            String</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    type              String</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    provider          String</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    providerAccountId String</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    refresh_token     String? // @db.Text</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    access_token      String? // @db.Text</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    expires_at        Int?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    token_type        String?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    scope             String?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    id_token          String? // @db.Text</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    session_state     String?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    user              User    @relation(fields: [userId], references: [id], onDelete: Cascade)</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    @@unique([provider, providerAccountId])</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">model Session {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    id           String   @id @default(cuid())</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    sessionToken String   @unique</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    userId       String</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    expires      DateTime</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">model User {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    id            String    @id @default(cuid())</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    name          String?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    email         String?   @unique</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    emailVerified DateTime?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    image         String?</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    accounts      Account[]</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    sessions      Session[]</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">model VerificationToken {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    identifier String</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    token      String   @unique</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    expires    DateTime</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    @@unique([identifier, token])</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">}</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></div></div></details>
<p>create-t3-app 默认使用的 sqlite 数据库，优点就是你无需安装任何数据库的环境，将会在 prisma 目录下创建 <code>db.sqlite</code> 文件来存放数据。但是缺点很明显，性能与部署方面是远不如主流服务级别的数据库。尤其是部署，这在后面会说。</p>
<p>将会创建 <code>Account</code> <code>Example</code> <code>Session</code> <code>User</code> <code>Verification Token</code> 表，这里需要教你一个命令</p>
<div class="language-bash codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-bash codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">npx prisma studio</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此时访问 localhost:5555 将会得到一个 prisma 面板，即项目的所有 model 。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/QBXnHdoewh.png" alt="" class="img_OpE3"></p>
<p>关于 prisma 更多命令请参考 <a href="https://www.prisma.io/docs/reference/api-reference/command-reference" target="_blank" rel="noopener noreferrer" title="Prisma CLI Command Reference">Prisma CLI Command Reference</a></p>
<p>prisma 在线体验：<a href="https://playground.prisma.io/" target="_blank" rel="noopener noreferrer">Prisma Playground | Learn the Prisma ORM in your browser</a></p>
<p>由于 create-t3-app 已经封装好了<a href="https://create.t3.gg/en/usage/prisma" target="_blank" rel="noopener noreferrer">数据库的操作</a>，并且导出 prisma 对象，所以你只需要配置好环境变量便可。</p>
<p>主要代码如下</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_">server/db.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> PrismaClient </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@prisma/client'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> prisma </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">PrismaClient</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="类型提示">类型提示<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E7%B1%BB%E5%9E%8B%E6%8F%90%E7%A4%BA" class="hash-link" aria-label="类型提示的直接链接" title="类型提示的直接链接">​</a></h4>
<p>在上面所定义的 model，都会被 prisma client 创建对应的 typescript 类型（在<code>node_modules/.prisma/index.d.ts</code>），你就可以直接通过 prisma.modelName 来操作 model，例如 Example（这里就不做注释了）</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> prisma </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'~/server/db'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">post</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">findUnique</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> where</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token number" style="color:hsl(35, 99%, 36%)">1</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">post</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">create</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> data</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">post</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">update</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> data</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">post</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">delete</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">post</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">count</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="数据迁移">数据迁移<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E6%95%B0%E6%8D%AE%E8%BF%81%E7%A7%BB" class="hash-link" aria-label="数据迁移的直接链接" title="数据迁移的直接链接">​</a></h4>
<p>我之前如果做数据库备份的话，我通常会在数据库管理软件（Navicat）将整个数据库转储为 SQL 文件，然后要用的时候在运行该 SQL 文件。而这样做呢虽然方便，但是数据都比较死，而且版本多了 sql 文件也多，导入繁琐。</p>
<p>此时就可以使用 <a href="https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/using-prisma-migrate-typescript-postgres" target="_blank" rel="noopener noreferrer">Migrate</a>，通过命令的方式自动为我们生成当前版本下的 sql 文件，而需要用到的也通过命令的形式运行 sql 文件。</p>
<h4 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="数据生成">数据生成<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E6%95%B0%E6%8D%AE%E7%94%9F%E6%88%90" class="hash-link" aria-label="数据生成的直接链接" title="数据生成的直接链接">​</a></h4>
<p>你可以编写一个 <a href="https://www.prisma.io/docs/guides/database/seed-database#example-seed-scripts" target="_blank" rel="noopener noreferrer">seed 脚本</a>，用于插种（生成）自定义数据。</p>
<hr>
<p>prisma 不是本文重点，篇幅略少，但是作为 Typeorm 的长期使用者而言，我认为 prisma 会比 typeorm 友善一些，至少从文档上来说 prisma 大胜一筹，而且很多 node 的 web 框架都优先 prisma 作为 orm 框架（除了 nest.js），但不过这两个仓库的 issues 数量有点惨不忍睹。。。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="next-auth">next-auth<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#next-auth" class="hash-link" aria-label="next-auth的直接链接" title="next-auth的直接链接">​</a></h3>
<p>我想先简单介绍一下 next-auth（背后由<a href="https://authjs.dev/" target="_blank" rel="noopener noreferrer" title="Auth.js">Auth.js</a> 提供）。</p>
<p>从名字来看也不难猜出，这是一个 next.js 的 auth 库。该库提供了多种身份验证策略，如基于密码的身份验证，OAuth 等等。并且你只需要简单的几行代码，提供好相关信息便可启用身份验证和授权功能。</p>
<p>你可以到这个网站 <a href="https://next-auth-example.vercel.app/" target="_blank" rel="noopener noreferrer" title="NextAuth.js Example">NextAuth.js Example</a>体验一番。下面是一些代码演示</p>
<p>由于 create-t3-app 默认是 Discord OAuth，因此我这边替换成使用者更多的 Github。（至于如何创建 Github OAuth Apps，在我之前的文章以及外面诸多文章中都有介绍到，这里不在演示了，附上配置图）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/_B1RYeiFze.png" alt="" class="img_OpE3"></p>
<p>首先在</p>
<p>server/auth.ts 中 导入</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>server/auth.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> CredentialsProvider </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'next-auth/providers/credentials'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> GithubProvider </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'next-auth/providers/github'</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>并在 options 中设置好 providers，如下</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockTitle_P25_"><span></span>server/auth.ts<span style="flex:1;text-align:right">typescript</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> authOptions</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> NextAuthOptions </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  callbacks</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token function" style="color:hsl(221, 87%, 60%)">session</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> session</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> user </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">session</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        session</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">id </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">id</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// session.user.role = user.role; &lt;-- put other properties on the session here</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> session</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  adapter</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">PrismaAdapter</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">prisma</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  providers</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token function" style="color:hsl(221, 87%, 60%)">CredentialsProvider</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'Credentials'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      credentials</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        username</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> label</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'Username'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'text'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> placeholder</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'kuizuo'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        password</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> label</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'Password'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> type</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'password'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">authorize</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">credentials</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token comment" style="color:hsl(230, 4%, 64%)">// Add logic here to look up the user from the credentials supplied</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> user </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> id</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'1'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> name</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'kuizuo'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> email</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'hi@kuizuo.me'</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">user</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> user</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">else</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">          </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">return</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">null</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">        </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token function" style="color:hsl(221, 87%, 60%)">GithubProvider</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      clientId</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> env</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">GITHUB_CLIENT_ID</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      clientSecret</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> env</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">GITHUB_CLIENT_SECRET</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>不过此时会提示 env 对象没有 GITHUB_CLIENT_ID 属性，需要在 env.mjs 定义好 GITHUB_CLIENT_ID 与 GITHUB_CLIENT_SECRET。类型安全嘛，你可不想 GITHUB 不小心输成 <del>GAYHUB</del> 导致找不到这个值把。</p>
<p>当上述在设置完毕后，点击 Sign in 按钮便可跳转到 next-auth 所提供的简单登录表单。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/9eowvvnwU2.png" alt="" class="img_OpE3"></p>
<p>如果你想自定义修改登录页面，可以参考该视频<a href="https://www.youtube.com/watch?v=kB6YNYZ63fw" target="_blank" rel="noopener noreferrer" title="Create your own next-auth Login Pages - YouTube">Create your own next-auth Login Pages - YouTube</a></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="部署-trpc">部署 tRPC<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E9%83%A8%E7%BD%B2-trpc" class="hash-link" aria-label="部署 tRPC的直接链接" title="部署 tRPC的直接链接">​</a></h2>
<p>通常来说 tRPC 会配合全栈框架使用，因此可以非常轻松的部署在 Vercel，Netlify 上。如今 Vercel 应该也已经家喻户晓了，因此这里就不演示如何部署，可到 <a href="https://create.t3.gg/en/deployment/vercel" target="_blank" rel="noopener noreferrer" title="Vercel • Create T3 App">Vercel • Create T3 App</a> 中查看相关步骤。</p>
<div class="theme-admonition theme-admonition-warning admonition_fh9h alert alert--warning"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_oz3Y"><p>不过要注意，Vercel 并不提供文件读写操作，即无法实现数据存储，因此你如果需要提供数据读取的操作，那么普通需要一个远程的数据库服务，将 DATABASE_URL 环境变量替换成线上地址。如</p><div class="language-title='env' codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-title='env' codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">DATABASE_URL=postgresql://myuser:mypassword@localhost:5432/mydb</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>这里推荐 <a href="https://railway.app/" target="_blank" rel="noopener noreferrer" title="railway">railway</a> 与 <a href="https://supabase.com/" target="_blank" rel="noopener noreferrer" title="supabase">supabase</a> 都提供远程数据服务，且有免费额度。（不过我比较好奇为啥好多远程数据服务多数都是 postgresql）</p><p>如果你执意要使用 vercel 部署，当你触发数据库服务时便会报错，以下是相关截图。</p><p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/7_XKmbuK87.png" alt="" class="img_OpE3"></p></div></div>
<p>至于说自行部署的话，create t3 app 提供了 docker 相关镜像，你可以直接使用 docker 部署，具体步骤可参考 <a href="https://create.t3.gg/en/deployment/docker" target="_blank" rel="noopener noreferrer">Docker • Create T3 App</a>。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="示例">示例<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E7%A4%BA%E4%BE%8B" class="hash-link" aria-label="示例的直接链接" title="示例的直接链接">​</a></h2>
<p>这里我提供了一个简单的示例，你可以 <a href="https://trpc.kuizuo.me/" target="_blank" rel="noopener noreferrer">点我</a> 访问体验一下（项目部署在 Vercel，而数据库服务在腾讯云，登录服务又依赖 Github，所以项目会稍微有那么慢）。整个项目结构大致如下</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/z_YaR-RnSu.png" alt="" class="img_OpE3"></p>
<p>你可以在 <a href="https://trpc.io/docs/example-apps" target="_blank" rel="noopener noreferrer" title="Example Apps | tRPC">Example Apps | tRPC</a> 查看 trpc 的示例应用。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="结语">结语<a href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc#%E7%BB%93%E8%AF%AD" class="hash-link" aria-label="结语的直接链接" title="结语的直接链接">​</a></h2>
<p>如果你是用 Next，Nuxt 等这样的全栈框架，并且你的后端服务使用 Typescript 编写，不妨试试 trpc，你会惊喜地发现，它颠覆了传统的 API 交互，使你的 typescript 全栈应用程序的开发变得更加高效和流畅。</p>
<p>从 JavaScript 到 TypeScript 的演变，全栈应用的端到端类型安全，TypeScript 目前正在逐渐成为前端开发中不可或缺的一部分，也许未来的某一天当人们说起前端三件套时，不再是 HTML，CSS，JavaScript，而是 HTML，CSS，TypeScript。</p>
<p>再说到我为何会去尝试 tRPC，有很大的原因是因为厌倦了传统后端开发，厌倦了 nest.js 开发。然而现实生活中，你所厌倦的，往往是能为你提供收入的。人们总是做着自己不愿做的事，但生活所迫，谁又愿意呢。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>trpc</category>
            <category>next</category>
            <category>prisma</category>
            <category>zod</category>
            <category>auth.js</category>
        </item>
        <item>
            <title><![CDATA[将 Supabase 作为下一个后端服务]]></title>
            <link>https://kuizuo.me/blog/use-supabase-as-backend-service</link>
            <guid>https://kuizuo.me/blog/use-supabase-as-backend-service</guid>
            <pubDate>Sat, 18 Feb 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[本文介绍了如何使用 Supabase 作为后端服务，使开发人员可以更快地构建和部署应用程序，无需配置数据库或编写复杂的身份验证代码。将使用 Nuxt.js 和 Supabase，以实现一个图床网站为例，来演示如何在前端中使用 Supabase API 和 Storage 服务。]]></description>
            <content:encoded><![CDATA[<p>对于想快速实现一个产品而言，如果使用传统开发，又要兼顾前端开发，同时又要花费时间构建后端服务。然而有这么一个平台（Baas Backend as a service）后端即服务，能够让开发人员可以专注于前端开发，而无需花费大量时间和精力来构建和维护后端基础设施。</p>
<p>对于只会前端的人来说，这是一个非常好的选择。后端即服务的平台使得开发人员能够快速构建应用程序，更快地将其推向市场。当然了，你可以将你的后端应用接入 Baas，这样你就无需配置数据库，编写复杂的身份效验。</p>
<p>如果你想了解 Baas，我想这篇文章或许对你有所帮助。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="什么是-supabase">什么是 <a href="https://supabase.com/" target="_blank" rel="noopener noreferrer" title="Supabase">Supabase</a>?<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E4%BB%80%E4%B9%88%E6%98%AF-supabase" class="hash-link" aria-label="什么是-supabase的直接链接" title="什么是-supabase的直接链接">​</a></h2>
<p>在摘要部分也介绍到名词 BaaS (Backend as a Service) ，意思为<strong>后端即服务</strong>。这个概念是在我接触 Serverless 的时候了解到的，更准确来说是腾讯云开发。当时在编写小程序的时候，只需要专注与应用业务逻辑，而不用编写数据存储，身份验证，文件存储等后端服务，这些统统由 BaaS 平台所提供。 通常会配合 Serverless 函数使用，通常也叫 FaaS（Function as a Service）。通常来说，FaaS 会依赖于 BaaS 平台。</p>
<p>而 Supabase 便是 BaaS 的平台之一。Supabase 是一个开源的 Firebase 替代品。使用 Postgres 数据库、身份验证、即时 API、边缘函数、实时订阅和存储启动项目。</p>
<p>你也许听过 Firebase，由 Google 提供的私有云服务，但开发者无法修改和扩展其底层代码。而 Supabase 是开源的，提供了类似 Firebase 的功能，且定价灵活，并且官方自称为 <a href="https://link.juejin.cn/?target=https://firebase.google.com/" target="_blank" rel="noopener noreferrer" title="Firebase">Firebase</a>的替代品。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="baas-与-cms-有何不同">BaaS 与 CMS 有何不同？<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#baas-%E4%B8%8E-cms-%E6%9C%89%E4%BD%95%E4%B8%8D%E5%90%8C" class="hash-link" aria-label="BaaS 与 CMS 有何不同？的直接链接" title="BaaS 与 CMS 有何不同？的直接链接">​</a></h2>
<p>BaaS 通常只专注于应用的后端服务，而 CMS 则是专注与内容管理。不过 BaaS 比较依赖云服务，而 CMS 通常只依赖于 web 后端技术。如果你想搭建一个内容站点（视频，音频，文章），并且作为网站管理员，那么 CMS 就是一个很好的选择，并且有相当多的主题模板。反之，不想搭建后端服务，减少运营程序，那么毫不犹豫的选择 BaaS。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="注册-supabase">注册 Supabase<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E6%B3%A8%E5%86%8C-supabase" class="hash-link" aria-label="注册 Supabase的直接链接" title="注册 Supabase的直接链接">​</a></h2>
<p>进入 <a href="https://app.supabase.com/sign-in" target="_blank" rel="noopener noreferrer">supabase 登录界面</a> 选择 Continue With Github</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_2yiQ9NHv21.png" alt="" class="img_OpE3"></p>
<p>输入 Github 账号密码进入<a href="https://app.supabase.com/projects" target="_blank" rel="noopener noreferrer" title="主页面">主页面</a>，新建一个项目</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_0eoOyP8DM2.png" alt="" class="img_OpE3"></p>
<p>为该项目起名，设置数据库密码，以及分配地区。</p>
<div class="theme-admonition theme-admonition-warning admonition_fh9h alert alert--warning"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_oz3Y"><p>创建 supabase 项目对密码要求非常严格，像 a123456 这种根本无法通过，像 ●●●●●●●●●● 密码就可以。</p><p>地区方面优先就近原则，而最近的也就是日本与韩国，很无奈 supabase 在大陆和港澳台并未设立服务器。</p></div></div>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_N5CQnx8cnU.png" alt="" class="img_OpE3"></p>
<p>等待片刻，你将拥有一个免费的后端服务！</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_Z33n9aUOC7.png" alt="" class="img_OpE3"></p>
<p>supabase 会提供一个二级域名供开发者访问，也就是这里 Project Configuration 的 URL，对应的这个二级域名 azlbliyjwcxxxxx 也就是你这个项目的唯一标识 Reference ID（下文称 项目 id）。你可以到 <a href="https://app.supabase.com/project/azlbliyjwcemojkwazto/settings/api" target="_blank" rel="noopener noreferrer" title="https://app.supabase.com/project/你的项目id/settings/api">https://app.supabase.com/project/你的项目 id/settings/api</a> 中查看相关配置。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="体验一下">体验一下<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E4%BD%93%E9%AA%8C%E4%B8%80%E4%B8%8B" class="hash-link" aria-label="体验一下的直接链接" title="体验一下的直接链接">​</a></h2>
<p>这里参考到了官方文档 <a href="https://supabase.com/docs/guides/database/api" target="_blank" rel="noopener noreferrer" title="Serverless APIs">Serverless APIs</a>。</p>
<p>首先，创建一个 todos 表，并新增字段（列）task 为 varchar 类型，Save 保存。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_Do9LHoUsYo.png" alt="" class="img_OpE3"></p>
<p>Insert row 添加一行记录，id 为 1，task 为 code。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_R9PEyH-spd.png" alt="" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_MLm6_i1Pb-.png" alt="" class="img_OpE3"></p>
<p>现在有了数据后，正常来说我们应该做什么？请求一下数据看看？不不不，应该是设置数据的权限。</p>
<p>打开到下图界面，我们要为 todos 数据新增一个 policy 策略。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_MEKk1-qQFl.png" alt="" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_W-C-pGNh1o.png" alt="" class="img_OpE3"></p>
<p>supabase 针对不同的场景提供了相应的策略方案模板，你也可以根据你的需求进行设置，这里作为演示不考虑太复杂，选择第一个允许任何人都可以请求到 todos 数据。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_Oa_424N4gz.png" alt="" class="img_OpE3"></p>
<p>接着下一步即可</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_wV_MqXQXcK.png" alt="" class="img_OpE3"></p>
<p>此时就新增了一个所有用户都可查询的 todo 的策略，同样的你还可以添加只有授权用户才能够创建更新删除 todo，更新与删除只能操作属于自己的 todo 资源。</p>
<p>这时候设置好了数据的权限后，就可以尝试去请求了，打开下图页面，将 URL 与 apikey 复制下来。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_GDEeyFCI2E.png" alt="" class="img_OpE3"></p>
<p>选择你一个 http 请求工具，这里我选用 <a href="https://hoppscotch.io/" target="_blank" rel="noopener noreferrer" title="hoppscotch">hoppscotch</a>，将信息填写上去，请求将会得到一开始所创建的 todo 数据。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_aSbRfmlwb9.png" alt="" class="img_OpE3"></p>
<p>除了 restful api 风格，还支持 graphql 风格，可查阅文档 <a href="https://supabase.com/docs/guides/database/api#using-the-api" target="_blank" rel="noopener noreferrer" title="Using the API">Using the API</a></p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_R0HtkYmznS.png" alt="" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="使用类库">使用类库<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E4%BD%BF%E7%94%A8%E7%B1%BB%E5%BA%93" class="hash-link" aria-label="使用类库的直接链接" title="使用类库的直接链接">​</a></h3>
<p>正常情况肯定不会像上面那样去使用，而是通过代码的方式进行登录，CRUD。这里使用 <a href="https://supabase.com/docs/reference/javascript/installing" target="_blank" rel="noopener noreferrer" title="Javascript Client Library">Javascript Client Library</a>，替我们封装好了 supabase 的功能。</p>
<p>首先，安装依赖</p>
<div class="language-bash codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-bash codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token function" style="color:hsl(221, 87%, 60%)">npm</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">install</span><span class="token plain"> @supabase/supabase-js</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>创建 客户端实例</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> createClient </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@supabase/supabase-js'</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此时准备好上述的 URL 与 apikey，用于创建 supabase 实例，不过 supabase 还提供 <a href="https://supabase.com/docs/reference/javascript/typescript-support" target="_blank" rel="noopener noreferrer">type 类型支持</a>，可以将生成的 <code>database.types.ts</code> 导入到实例中，如</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> createClient </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'@supabase/supabase-js'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> Database </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'lib/database.types'</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> supabase </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:hsl(221, 87%, 60%)">createClient</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token generic-function generic class-name" style="color:hsl(35, 99%, 36%)">Database</span><span class="token generic-function generic class-name operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">process</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">env</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">SUPABASE_URL</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> process</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">env</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token constant" style="color:hsl(35, 99%, 36%)">SUPABASE_ANON_KEY</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>此时有了 supabse 对象后，就能够请求数据了，像上述通过 http 的方式获取 todos 数据，在这里对应的代码为</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> data</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> error </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> supabase</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">from</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'todos'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">select</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p><a href="https://supabase.com/docs/reference/javascript/select" target="_blank" rel="noopener noreferrer">官方的演示例子</a> 非常清晰，这里就不在演示新增更新等示例。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image-20230218182910913.png" alt="image-20230218182910913" class="img_OpE3"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="supabase-主要功能"><a href="https://supabase.com/docs" target="_blank" rel="noopener noreferrer">Supabase 主要功能</a><a href="https://kuizuo.me/blog/use-supabase-as-backend-service#supabase-%E4%B8%BB%E8%A6%81%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="supabase-主要功能的直接链接" title="supabase-主要功能的直接链接">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="database-数据库">Database 数据库<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#database-%E6%95%B0%E6%8D%AE%E5%BA%93" class="hash-link" aria-label="Database 数据库的直接链接" title="Database 数据库的直接链接">​</a></h3>
<p>supabase 基于 PostgreSQL 数据库，因此当你创建完项目后，就自动为你分配好了一个可访问的 PostgreSQL 数据库，你完全可以将其当做一个远程的 PostgreSQL 数据主机。</p>
<p>可以在如下页面中查看到有关数据库连接的信息，当然你看不到密码。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_6uCHh3qrlE.png" alt="" class="img_OpE3"></p>
<p>测试连接，结果如下，并无问题</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_8-JOTiLI0G.png" alt="" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="authentication-身份验证">Authentication 身份验证<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#authentication-%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81" class="hash-link" aria-label="Authentication 身份验证的直接链接" title="Authentication 身份验证的直接链接">​</a></h3>
<p><a href="https://supabase.com/docs/guides/auth/overview" target="_blank" rel="noopener noreferrer" title="Auth | Supabase Docs">Auth | Supabase Docs</a></p>
<p>supabase 令我感兴趣的是 <a href="https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security" target="_blank" rel="noopener noreferrer" title="Row Level Security">Row Level Security</a>，supabase 使用 Postgres 的 Row-Level-Security（行级安全）策略，可以限制不同用户对同一张表的不同数据行的访问权限。这种安全机制可以确保只有授权用户才能访问其所需要的数据行，保护敏感数据免受未授权的访问和操作。</p>
<p>在传统的访问控制模型中，用户通常只有对整个表的访问权限，无法限制他们对表中特定数据行的访问。而行级安全技术则通过将访问权限授予到特定的数据行，从而让不同的用户只能访问他们被授权的行。这种行级安全有一个很经典应用场景-多租户系统：允许不同的客户在同一张表中存储数据，但每个客户只能访问其自己的数据行。</p>
<p>这对于传统后端开发而言，如果不借用一些安全框架，实现起来十分棘手，要么业务代码与安全代码逻辑混杂不堪。</p>
<p>权限细分方面，无需担心，supabase 已经为你做好了准备，就等你来进行开发。</p>
<h4 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="第三方登录">第三方登录<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E7%AC%AC%E4%B8%89%E6%96%B9%E7%99%BB%E5%BD%95" class="hash-link" aria-label="第三方登录的直接链接" title="第三方登录的直接链接">​</a></h4>
<p>对于想要提供第三方登录，supabse 集成多数平台（除了国内），只需要提供 Clinet ID, Client Secret, Redirect URL 便可完成第三方登录。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_OvBRJ_elZR.png" alt="" class="img_OpE3"></p>
<p>这里演示下如何使用 Github，首先到打开<a href="https://github.com/settings/applications/new" target="_blank" rel="noopener noreferrer" title="New OAuth Application (github.com)">New OAuth Application (github.com)</a> 创建一个 Oauth Apps，其中 Authorization callback URL 由 supabase 提供，如下图。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_QVspy-oxQK.png" alt="" class="img_OpE3"></p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_jyaUMSDed2.png" alt="" class="img_OpE3"></p>
<p>当你创建完后，会提供 Client ID，与 Client secret，将这两个值填写到 supabase 中，并启用。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_QpRRxpR5o5.png" alt="" class="img_OpE3"></p>
<p>此时打开如下页面，将 Site URL 替换成开发环境，或是线上环境，在 Github 登录后将会跳转到这个地址上</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_zmfXC85ayC.png" alt="" class="img_OpE3"></p>
<p>此时 supabase 支持 github 登录就已经配置完毕，当你在前端触发登录按钮后，借助<a href="https://supabase.com/docs/reference/javascript/auth-signinwithoauth" target="_blank" rel="noopener noreferrer" title="supabase 的js库">supabase 的 js 库</a>，如</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> data</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> error </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">await</span><span class="token plain"> supabase</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">auth</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">signInWithOAuth</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  provider</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'github'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>便可完成 Github 第三方登录。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="bucket-存储桶">Bucket 存储桶<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#bucket-%E5%AD%98%E5%82%A8%E6%A1%B6" class="hash-link" aria-label="Bucket 存储桶的直接链接" title="Bucket 存储桶的直接链接">​</a></h3>
<p>接触过对象存储的开发者对 Bucket 应该不陌生，相当于给你一个云盘，这里演示如何使用。</p>
<p>打开如下界面，这里选择公开存储桶，比如说用于图床。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_2Is4Bfwf8f.png" alt="" class="img_OpE3"></p>
<p>点击右上角的 upload files，选择你要上传的图片。你可以为此生成一个访问 URL</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_vkuzeZZVJ_.png" alt="" class="img_OpE3"></p>
<p>你可以访问 <a href="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/publilc/1.png" target="_blank" rel="noopener noreferrer">1.png</a> 来查看这张图片。如果是公开的话 一般都是类似<a href="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/new-bucket/1.png" target="_blank" rel="noopener noreferrer">https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/new-bucket/1.png</a></p>
<p>而私有的为 <a href="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/sign/new-bucket/1.png?token=eyJhbGciOiJIUzI1NiIsInR5cCIxxxxxxxxxxxxxxxxx" target="_blank" rel="noopener noreferrer">https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/sign/new-bucket/1.png?token=eyJhbGciOiJIUzI1NiIsInR5cCIxxxxxxxxxxxxxxxxx</a> 路径稍微变化了下，还有就是多了个 token，如果不携带 token 则访问不了图片。</p>
<p>你可以到<a href="https://supabase.github.io/storage-api/" target="_blank" rel="noopener noreferrer" title="Supabase Storage API">Supabase Storage API</a> 查看 storage 相关 api。</p>
<div class="theme-admonition theme-admonition-tip admonition_fh9h alert alert--success"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>现学现用</div><div class="admonitionContent_oz3Y"><p>本文中的所有图片数据都来源于 supabase bucket。</p></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="edge-functions-边缘函数">Edge Functions 边缘函数<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#edge-functions-%E8%BE%B9%E7%BC%98%E5%87%BD%E6%95%B0" class="hash-link" aria-label="Edge Functions 边缘函数的直接链接" title="Edge Functions 边缘函数的直接链接">​</a></h3>
<p>边缘函数可以分布在全球的接近您的用户各个地方，类似与 CDN，但 CDN 主要服务于静态资源，而 Edge Functions 可以将你的后端应用接口，像 CDN 那样部署到全球各地。</p>
<p>有兴趣可自行了解。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="使用-supabase-编写一个简易图床"><strong>使用 Supabase 编写一个简易图床</strong><a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E4%BD%BF%E7%94%A8-supabase-%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E6%98%93%E5%9B%BE%E5%BA%8A" class="hash-link" aria-label="使用-supabase-编写一个简易图床的直接链接" title="使用-supabase-编写一个简易图床的直接链接">​</a></h2>
<p>如果只单纯看看 supabase 文档，不去动手实践接入一下，总觉得还是差点意思。于是我准备使用 Nuxt 作为前端框架接入 supabase，官方模块 <a href="https://supabase.nuxtjs.org/" target="_blank" rel="noopener noreferrer" title="Nuxt Supabase">Nuxt Supabase</a> 去编写一个应用。</p>
<p>原本我是打算写个 Todo List 的（恼，怎么又是 Todo List），但是看到 <a href="https://supabase.com/docs/guides/resources/examples#official-examples" target="_blank" rel="noopener noreferrer" title="官方示例">官方示例</a>（一堆 Todo List）后我瞬间就没了兴致 🥀。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_1polvJf0q0.png" alt="" class="img_OpE3"></p>
<p>思来想去，不妨就简单做个图床吧。项目地址：<a href="https://image.kuizuo.me/" target="_blank" rel="noopener noreferrer">https://image.kuizuo.me</a> 有兴趣可自行阅读<a href="https://github.com/kuizuo/image-hosting" target="_blank" rel="noopener noreferrer">源码</a>。（<strong>写的相对匆忙，仅作为演示，随时有可能删除，请勿将此站作为永久图床！</strong>）</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="一些你可能比较好奇的问题">一些你可能比较好奇的问题<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E4%B8%80%E4%BA%9B%E4%BD%A0%E5%8F%AF%E8%83%BD%E6%AF%94%E8%BE%83%E5%A5%BD%E5%A5%87%E7%9A%84%E9%97%AE%E9%A2%98" class="hash-link" aria-label="一些你可能比较好奇的问题的直接链接" title="一些你可能比较好奇的问题的直接链接">​</a></h2>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="资源">资源<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E8%B5%84%E6%BA%90" class="hash-link" aria-label="资源的直接链接" title="资源的直接链接">​</a></h3>
<p>可以到 <a href="https://app.supabase.com/project/%E9%A1%B9%E7%9B%AEid/settings/billing/usage" target="_blank" rel="noopener noreferrer">https://app.supabase.com/project/项目id/settings/billing/usage</a> 中查看相关资源使用情况，这里我就将截图放出来了。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_Bllhp6XlFz.png" alt="" class="img_OpE3"></p>
<p>说实话，对于个人独立开发者的项目都绰绰有余了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="费用">费用<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E8%B4%B9%E7%94%A8" class="hash-link" aria-label="费用的直接链接" title="费用的直接链接">​</a></h3>
<p>在 <a href="https://supabase.com/pricing" target="_blank" rel="noopener noreferrer" title="资费标准">资费标准</a> 中可以看到，免费版<strong>最多 2 个项目</strong>，不过在上述的资源，其实已经非常香了，毕竟只需要一个 GIthub 账号就能免费使用，还要啥自行车。</p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_MNtdzsdJ2t.png" alt="" class="img_OpE3"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="网速">网速<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E7%BD%91%E9%80%9F" class="hash-link" aria-label="网速的直接链接" title="网速的直接链接">​</a></h3>
<p>国内因为没有 supabase 的服务器节点，然后且有防火墙的存在，所以请求速度偏慢。不过体验下来至少不用梯子，速度慢点但也还在可接受范围。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="域名">域名<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E5%9F%9F%E5%90%8D" class="hash-link" aria-label="域名的直接链接" title="域名的直接链接">​</a></h3>
<p>用过 vercel 的你应该会想是不是也能自定义域名呢? 当然，不过这是 supabase pro 版才支持，一个月$25(美刀)，算了算了，再一眼 azlbliyjwcxxxxx.supabase.co<del>就会爆炸</del>感觉也蛮好记的。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="结语">结语<a href="https://kuizuo.me/blog/use-supabase-as-backend-service#%E7%BB%93%E8%AF%AD" class="hash-link" aria-label="结语的直接链接" title="结语的直接链接">​</a></h2>
<p>说句实话，真心感觉 supabase 不错，尤其是对个人/独立开发者而言，没必要自行去购买服务器，去搭建后端服务，很多时候我们只想专注于应用程序的开发和功能实现，而不是花费大量时间和精力在服务器和后端服务的部署和管理上。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>supabase</category>
            <category>nuxt</category>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[🍋 使用 Fresh 框架构建Web 应用]]></title>
            <link>https://kuizuo.me/blog/use-fresh-build-web-applicatioin</link>
            <guid>https://kuizuo.me/blog/use-fresh-build-web-applicatioin</guid>
            <pubDate>Wed, 15 Feb 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[使用 Fresh 框架构建Web 应用，用于将链接转换为卡片样式的预览效果图。]]></description>
            <content:encoded><![CDATA[<p>这篇文章将使用 deno 的 web 框架 Fresh，一个简单的 Web 应用 <a href="https://link-maker.deno.dev/" target="_blank" rel="noopener noreferrer" title="Link Maker">Link Maker</a>，一个用于将链接转换成卡片样式的预览效果。</p>
<p>这个项目也放在了 fresh 的 <a href="https://fresh.deno.dev/showcase" target="_blank" rel="noopener noreferrer" title="Showcase">Showcase</a>，感兴趣的可以查看一番。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="什么是-fresh">什么是 fresh？<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#%E4%BB%80%E4%B9%88%E6%98%AF-fresh" class="hash-link" aria-label="什么是 fresh？的直接链接" title="什么是 fresh？的直接链接">​</a></h2>
<p><a href="https://fresh.deno.dev/" target="_blank" rel="noopener noreferrer">fresh</a> 自称是下一代 web 开发框架（这句话怎么这么熟悉?），是一个基于 Deno 的 Web 框架。它提供了许多用于构建 Web 应用程序和 API 的工具和功能。Fresh 框架特别强调简单性和灵活性，并着重于提供最佳的性能和开发体验。它支持 TypeScript，并且不需要任何配置或构建步骤。这些特性使得 Fresh 框架成为构建高效和现代 Web 应用程序的理想选择。</p>
<div class="theme-admonition theme-admonition-warning admonition_fh9h alert alert--warning"><div class="admonitionHeading__rZX"><span class="admonitionIcon_krpS"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Fresh 的前端渲染层由 Preact 完成，包括 Islands 架构的实现也是基于 Preact。如果你想在 Fresh 中使用其他主流前端框架，目前来说有点无能为力。</div></div>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="创建-fresh-项目">创建 fresh 项目<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#%E5%88%9B%E5%BB%BA-fresh-%E9%A1%B9%E7%9B%AE" class="hash-link" aria-label="创建 fresh 项目的直接链接" title="创建 fresh 项目的直接链接">​</a></h2>
<p><a href="https://fresh.deno.dev/docs/getting-started/create-a-project" target="_blank" rel="noopener noreferrer" title="Create a project | fresh docs">Create a project | fresh docs</a></p>
<p>deno 提供了非常友好的创建 fresh 项目的命令，运行:</p>
<div class="language-bash codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-bash codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">deno run </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">-A</span><span class="token plain"> </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">-r</span><span class="token plain"> https://fresh.deno.dev my-project</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token builtin class-name" style="color:hsl(35, 99%, 36%)">cd</span><span class="token plain"> my-project</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">deno task start</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>根据你的喜好进行配置，如下</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/jSRfPu966v.png" alt="" class="img_OpE3"></p>
<p>此时会创建如下文件</p>
<div class="language-bash codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-bash codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">my-project</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── components        </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># 组件</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   └── Button.tsx    </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># 按钮组件</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── deno.json         </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># deno配置文件</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── dev.ts            </span><span class="token comment" style="color:hsl(230, 4%, 64%)">#</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── fresh.gen.ts      </span><span class="token comment" style="color:hsl(230, 4%, 64%)">#</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── import_map.json   </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># 依赖导入映射</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── islands           </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># 群岛(组件群岛)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   └── Counter.tsx</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── main.ts           </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># 入口文件</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── routes            </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># 路由</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   ├── </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">name</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain">.tsx</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   ├── api</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   │   └── joke.ts</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   └── index.tsx</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">├── static            </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># 静态资源</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   ├── favicon.ico</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">│   └── logo.svg</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">└── twind.config.ts   </span><span class="token comment" style="color:hsl(230, 4%, 64%)"># twind配置文件</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>介绍几个文件：</p>
<ul>
<li><strong><code>dev.ts</code></strong>: 项目开发模式的匹配文件，假设你需要区分生产环境和开发环境，就可以通过 dev.ts，prod.ts 命令来指明入口</li>
<li><strong><code>main.ts</code></strong>: 入口文件，会用于链接 <a href="https://deno.com/deploy" target="_blank" rel="noopener noreferrer">Deno Deploy</a>。</li>
<li><strong><code>fresh.gen.ts</code></strong>: 这个清单文件会基于<code>routes/</code> 和 <code>islands/</code> 文件夹自动生成。包含项目的 route 和 island 信息。</li>
<li><strong><code>import_map.json</code></strong>: 这是用于管理项目的依赖项的导入映射。这允许轻松地导入和更新依赖项。</li>
</ul>
<p>其中最主要的两个目录，这里会细说。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="routes">routes<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#routes" class="hash-link" aria-label="routes的直接链接" title="routes的直接链接">​</a></h3>
<p><strong><code>routes/</code></strong>: 存放项目中的所有路由。文件即路由，每个文件的名称对应于访问该页的路径。注：此文件夹中的代码永远不会直接发送到客户端.</p>
<p>其中 routes/api 通常存放一些 api 接口，这这里你完全可以将其当做一个 deno 的服务端，可以做后端能做的事情，通常来说就是提供一个可请求的 api 接口。</p>
<p>而其他文件就相当于一个可访问的页面组件，同样是文件路由系统，也可以在这里进行 SSR、中间件操作。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="islands">islands<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#islands" class="hash-link" aria-label="islands的直接链接" title="islands的直接链接">​</a></h3>
<p><strong><code>Islands/</code></strong>: 群岛，Fresh 中我并未看到对这一词的解释，你可以到 <a href="https://docs.astro.build/zh-cn/concepts/islands/" target="_blank" rel="noopener noreferrer">astro 群岛</a> 看看新的 Web 架构模式，主要作用就是用于存放交互式组件（服务端组件），可以在客户端和服务端运行。有点类似与 next.js 的服务端组件，同样有两种状态（服务端，浏览器端）。</p>
<p>这一部分会有点难理解，你只要知道 IsLands 存放的组件有两种状态（服务端，浏览器端），下文称服务端组件，不同于 components 下的组件，服务端组件有一些优势，例如说</p>
<ul>
<li>可以直接访问服务端相关资源</li>
<li>避免了不必要的客户端和服务端之间的交互，因此性能更快</li>
<li>允许一些类库可以直接运行在服务端，因此减小了客户端包文件的大小</li>
</ul>
<p><strong>想要真正理解服务端组件，就不得不将其与 SSR 拿来对比了。</strong></p>
<p>SSR 通常是将数据通过服务端的前端框架渲染成 HTML，直接将 HTML 返回给客户端就可以省去 xhr/fetch 请求的过程，只需要首次请求就能得到数据。此时页面交互，数据更新与传统的前端应用没有任何区别，<strong>通俗点说 SSR 就是省去 xhr/fetch 请求的过程</strong>。</p>
<p>而服务端组件会在服务端完成渲染，然后通过自定义的协议发送到客户端。前端应用会将新的 UI 整体（服务端组件）的合并到客户端 UI 树里面（也有叫 hydration 水合），此过程不会对客户端其他状态产生影响，还能达到保持客户端状态的目的，极大的增强了用户体验。</p>
<p>如果你仔细查看控制面板的网络请求输出，可以看到服务器端组件是可以请求的。（这里用的后面实战的截图作为展示）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/v73eXB47yI.png" alt="" class="img_OpE3"></p>
<p>不过既然服务端组件也有很多限制，就比如说服务端状态下，是无法使用 Web 相关 Api 的，数据传输（通过 props）是有前提的，要 JSON 可序列化，也就是说只能传递基本类型、基本对象、数组，像 Date，自定义类，函数等复制对象是无法传递的。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="实战">实战<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#%E5%AE%9E%E6%88%98" class="hash-link" aria-label="实战的直接链接" title="实战的直接链接">​</a></h2>
<p>项目还是相对比较简单的，将链接转化为一个卡片样式的预览效果（包含链接的标题，图片，描述）。</p>
<p>核心代码在 <a href="https://github.com/kuizuo/link-maker/blob/main/routes/api/link.ts" target="_blank" rel="noopener noreferrer"><code>routes\api\link.ts</code></a> 下，将会生成 <code>/api/link</code> 接口，例如访问 <a href="https://link-maker.deno.dev/api/link?q=https://kuizuo.me" target="_blank" rel="noopener noreferrer" title="https://link-maker.deno.dev/api/link?q=https://kuizuo.me">https://link-maker.deno.dev/api/link?q=https://kuizuo.me</a> 你就可以得到如下 json 数据</p>
<div class="language-json codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-json codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token property" style="color:hsl(5, 74%, 59%)">"title"</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"愧怍的小站"</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token property" style="color:hsl(5, 74%, 59%)">"description"</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"Blog"</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token property" style="color:hsl(5, 74%, 59%)">"image"</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"https://kuizuo.me/img/logo.png"</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token property" style="color:hsl(5, 74%, 59%)">"url"</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">"https://kuizuo.me"</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>原理就是通过 fetch 请求目标 url，通常来说得到的是一个 html 页面，这时使用 <a href="https://deno.land/x/deno_dom@v0.1.36-alpha/deno-dom-wasm.ts" target="_blank" rel="noopener noreferrer" title="deno-dom">deno-dom</a> 解析成 Dom 对象，通过 css 选择器选取所要的数据，并整合返回给调用方。</p>
<p>有了这个接口，剩下的前端工作就相对比较轻松了，主要也就是细节话的问题。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="坑点不足">坑点/不足<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#%E5%9D%91%E7%82%B9%E4%B8%8D%E8%B6%B3" class="hash-link" aria-label="坑点/不足的直接链接" title="坑点/不足的直接链接">​</a></h2>
<p>下面我会说说,在我编写该应用的时候，有哪些开发体验上的不足之处，如果你恰好有使用 Fresh 框架编写 Web 应用的话，最好需要注意下。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="vscode-下对-deno-项目重构并不友好">vscode 下对 deno 项目重构并不友好<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#vscode-%E4%B8%8B%E5%AF%B9-deno-%E9%A1%B9%E7%9B%AE%E9%87%8D%E6%9E%84%E5%B9%B6%E4%B8%8D%E5%8F%8B%E5%A5%BD" class="hash-link" aria-label="vscode 下对 deno 项目重构并不友好的直接链接" title="vscode 下对 deno 项目重构并不友好的直接链接">​</a></h3>
<p>当我移动项目 .ts/.tsx 文件的时候，vscode 会将该文件与其他引用该文件的路径更改为 .js/.jsx，这就比较蛋疼了，所以每当要移动文件的时候都要尤为小心。</p>
<p>还有就是文件的依赖关系不是那么准确，尤其是在首次进入项目工程的时候，比如说在 routes/test.tsx 中 导入了 <code>components/Button.tsx</code> 组件，当你在 tsx 中写了<code>&lt;Button&gt;&lt;/Button&gt;</code> ，vscode 并不会有任何的引入提示，当你打开 <code>components/Button.tsx</code> 文件后就又有了，搞得我都怀疑是不是没有该组件。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="无法直接通过上下文获取-query-参数">无法直接通过上下文获取 query 参数<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#%E6%97%A0%E6%B3%95%E7%9B%B4%E6%8E%A5%E9%80%9A%E8%BF%87%E4%B8%8A%E4%B8%8B%E6%96%87%E8%8E%B7%E5%8F%96-query-%E5%8F%82%E6%95%B0" class="hash-link" aria-label="无法直接通过上下文获取 query 参数的直接链接" title="无法直接通过上下文获取 query 参数的直接链接">​</a></h3>
<p>fresh 的 handler 提供两个参数，一般来都会写成下面这种形式，可以区分 Get，Post 请求</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">export</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> handler </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token constant" style="color:hsl(35, 99%, 36%)">GET</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">req</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Request</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> ctx</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> HandlerContext</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">Promise</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">Response</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">async</span><span class="token plain"> </span><span class="token constant" style="color:hsl(35, 99%, 36%)">POST</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">req</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Request</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> ctx</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> HandlerContext</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token builtin" style="color:hsl(119, 34%, 47%)">Promise</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">Response</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>假设当前的请求是 /api/test?q=123，我想要获取 query 参数的 q，我得这么做</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> url </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">new</span><span class="token plain"> </span><span class="token class-name constant" style="color:hsl(35, 99%, 36%)">URL</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">req</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">url</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token plain"> q </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> url</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token plain">searchParams</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">get</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'q'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>当时我尝试用 ctx.query 和 req.query 来获取 q 参数，然而 ctx 与 req 并没有 query 属性，在翻阅文档与源码，才得知 fresh 并没有将 query 参数解析到 req 或 ctx 下。</p>
<p>至于说为何要用 query 而不是用 param，主要是因为 url 的缘故，比如说 <code>/api/link/https://kuizuo.me</code> 这个链接，这时 param 是解析不出 <code>https://kuizuo.me</code> 完整 url 的，除非 url 编码，但这对使用者来说就不是很好，于是就舍弃了 param 参数的方案。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="有些-npm-包在-fresh-无法正常使用">有些 npm 包在 fresh 无法正常使用<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#%E6%9C%89%E4%BA%9B-npm-%E5%8C%85%E5%9C%A8-fresh-%E6%97%A0%E6%B3%95%E6%AD%A3%E5%B8%B8%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="有些 npm 包在 fresh 无法正常使用的直接链接" title="有些 npm 包在 fresh 无法正常使用的直接链接">​</a></h3>
<p>在这个应用中我所使用到了 <a href="https://www.npmjs.com/package/html2canvas" target="_blank" rel="noopener noreferrer" title="html2canvas">html2canvas</a> 库用于将页面的 div 元素转成 canvas，以便转成图片的形式并下载。然后在我导入的时候，要么提示找不到该包（大概率是因为 Commonjs），要么就是 html2canvas 不存在，最终无奈我只好将 html2canvas.min.js 存放在 static 下，并在页面中通过 <code>&lt;script src="/js/html2canvas.min.js"&gt;&lt;/script&gt;</code> 方式导入，这样全局有了 html2canvas 就可使用。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="islands-下的组件要时刻注意-web-api-调用">islands 下的组件要时刻注意 Web Api 调用<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#islands-%E4%B8%8B%E7%9A%84%E7%BB%84%E4%BB%B6%E8%A6%81%E6%97%B6%E5%88%BB%E6%B3%A8%E6%84%8F-web-api-%E8%B0%83%E7%94%A8" class="hash-link" aria-label="islands 下的组件要时刻注意 Web Api 调用的直接链接" title="islands 下的组件要时刻注意 Web Api 调用的直接链接">​</a></h3>
<p>我在 islands 下的组件中用到了 localStorage 用于持久化数据，然而在我尝试部署到服务器上的时候发现网站无法访问，并在错误日志中提示 localStorage is not defined。</p>
<p>其实这在很多 hydration 框架中都有这一个问题，在 islands 下的组件有两种状态（浏览器端，服务端），后文就称为客户端组件和服务端组件。也正是如此，服务端组件是没有客户端的运行时环境，就比如说你想要在组件中使用 localStorage 对象用来持久化数据，在两种状态下，首先会在服务端执行一遍，然而服务端并没有 localStorage 对象，此时就会提示 localStorage is not defined。</p>
<p>通常的做法是判断组件当前的状态，可以用如下方式来判断是否为浏览器环境。</p>
<div class="language-typescript codeBlockContainer_APcc theme-code-block" style="--prism-background-color:hsl(230, 1%, 98%);--prism-color:hsl(230, 8%, 24%)"><div class="codeBlockContent_m3Ux"><pre class="prism-code language-typescript codeBlock_qGQc thin-scrollbar" style="background-color:hsl(230, 1%, 98%);color:hsl(230, 8%, 24%)"><code class="codeBlockLines_p187"><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token keyword" style="color:hsl(301, 63%, 40%)">import</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> </span><span class="token constant" style="color:hsl(35, 99%, 36%)">IS_BROWSER</span><span class="token plain"> </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">from</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'$fresh/runtime.ts'</span><br></span></code></pre><div class="buttonGroup_6DOT"><button type="button" aria-label="复制代码到剪贴板" title="复制" class="clean-btn"><span class="copyButtonIcons_FhaS" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_phi_"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_FfTR"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>然后将 localStorage 等 Web 相关 API 的调用放在 IS_BROWSER 的判断中。</p>
<p>有篇相关文件非常值得阅读，或许对组件的 hydration 有更好的理解</p>
<p><a href="https://blog.somewhatabstract.com/2020/03/16/hydration-and-server-side-rendering/" target="_blank" rel="noopener noreferrer" title="💧 Hydration and Server-side Rendering – somewhat abstract">💧 Hydration and Server-side Rendering – somewhat abstract</a></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="前端框架比较局限">前端框架比较局限<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E6%AF%94%E8%BE%83%E5%B1%80%E9%99%90" class="hash-link" aria-label="前端框架比较局限的直接链接" title="前端框架比较局限的直接链接">​</a></h2>
<p>在前面也说过，Fresh 的前端渲染层由 Preact 完成。如果用户要用 React/Vue 那为何不选择生态更好的 next.js/nuxt.js 呢？所以目前来看，Fresh 还是有些无能为力。但可以肯定的是，fresh 的方向与 next.js/nuxt.js 的一致。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="部署">部署<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#%E9%83%A8%E7%BD%B2" class="hash-link" aria-label="部署的直接链接" title="部署的直接链接">​</a></h2>
<p><a href="https://dash.deno.com/" target="_blank" rel="noopener noreferrer" title="deno Deploy">deno Deploy</a> 可以非常轻松的部署 fresh 应用，使用 Github 账号登录后，<a href="https://dash.deno.com/new" target="_blank" rel="noopener noreferrer" title="New Project">New Project</a>，从 github 仓库中拉取项目点击 Link 即可部署完毕。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/CYOAgv6IGe.png" alt="" class="img_OpE3"></p>
<p>这里的项目名为 link-maker，那么就会生成 专属访问链接 <a href="https://link-maker.deno.dev/" target="_blank" rel="noopener noreferrer" title="https://link-maker.deno.dev">https://link-maker.deno.dev</a>（也许要梯子才能访问）</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_j5ym" id="结语">结语<a href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin#%E7%BB%93%E8%AF%AD" class="hash-link" aria-label="结语的直接链接" title="结语的直接链接">​</a></h2>
<p>最后，在我编写完该应用后，我对其做一个评价吧。收回一开始的一句话，<del>fresh 自称是下一代 web 开发框架</del>。</p>
<p>如果要让我在 next.js 和 fresh 两个相似的产品中做个选择的话，我肯定毫不犹豫的选择 next.js。一个以一己之力推动了前端的发展，到至今已有越来越多的项目使用 next.js ，我想作为任何一个前端学习者肯定会毫不犹豫的选择 next.js 去编写 web 应用。</p>
<p>就从用户的开发体验而言，就已经很难让我再次选择 fresh，更何况还有像 next.js/nuxt.js 这样的全栈框架。作为一个开发体验（Developer experience）优先的程序员角度来看，如果一个框架想要让别人广泛使用，一定要满足其开发过程，只有沉浸于此，才能不断思考，编写出高质量代码。即便无负担的配置，高性能编译，轻便的部署，这些在他人看来可选择的点（也是 fresh 的点），在我看来却显得很微不足道。</p>
<p>而为什么我会选择尝试 fresh，其实也就想看看能不能找到一个令我眼前一亮的一个全栈 Web 框架，然而目前来看，fresh 还有很长一段距离。</p>]]></content:encoded>
            <author>hi@kuizuo.me (愧怍)</author>
            <category>deno</category>
            <category>fresh</category>
            <category>web</category>
            <category>project</category>
        </item>
    </channel>
</rss>