<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://kuizuo.me/blog</id>
    <title>愧怍</title>
    <updated>2024-12-31T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://kuizuo.me/blog"/>
    <subtitle>feedId:41215011978385457+userId:41840354283324416</subtitle>
    <icon>https://kuizuo.me/img/favicon.ico</icon>
    <rights>Copyright © 2025 愧怍 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;</rights>
    <entry>
        <title type="html"><![CDATA[2024 · 应届牛马到远程自由]]></title>
        <id>https://kuizuo.me/blog/2024-year-end-summary</id>
        <link href="https://kuizuo.me/blog/2024-year-end-summary"/>
        <updated>2024-12-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[面临着最后半年的大学生涯，与大多数大学生不同，我无时不在期盼着毕业这天的到来。步入职场进入了一家 AI 初创公司开始了"牛马"生活。最终我选择在国庆前裸辞，回到老家开始黑猴 play，却未曾料到，一份远程工作机会和一位新伙伴🐱悄然而至…]]></summary>
        <content type="html"><![CDATA[<p>面临着最后半年的大学生涯，与大多数大学生不同，我无时不在期盼着毕业这天的到来。步入职场进入了一家 AI 初创公司开始了"牛马"生活。最终我选择在国庆前裸辞，回到老家开始黑猴 play，却未曾料到，一份远程工作机会和一位新伙伴🐱悄然而至…</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></p>
<p>后面一起去深圳参加了 VueConf 大会，也是我第一次参加这种大型技术活动。之后还计划一同去🇭🇰搞卡，然而和去年一样，由于特殊原因，今年的我依旧办理不了港澳通行证😔</p>
<p>下半年参加 Feday 有幸见到了一些大佬，技术的氛围感，让我感受到了一种前所未有的激动。明年的我一定要面基更多的网友，去更多的地方，体验更多的生活。</p>
<p>结束了短暂的游玩时光，终于要开始苦逼的牛马生活了，吗？</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></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_vMh4" 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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="年终总结" term="年终总结"/>
        <category label="工作" term="工作"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Next.js 使用 Hono 接管 API]]></title>
        <id>https://kuizuo.me/blog/nextjs-with-hono</id>
        <link href="https://kuizuo.me/blog/nextjs-with-hono"/>
        <updated>2024-10-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[这篇文章详细介绍了如何在 Next.js 项目中使用 Hono 框架来接管 API 路由，以解决 Next.js 自带 API Routes 功能的限制。并探讨了集成步骤、数据验证、错误处理、RPC功能等方面，并提供了实用的代码示例和优化建议。]]></summary>
        <content type="html"><![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_c29J"></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_vMh4" 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_c29J"></p>
<p>至此 next.js 的 api 接口都将由 hono.js 来接管，接下来只需要按照 Hono 的开发形态便可。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<p>所返回的响应体是完整的 zodError 内容，并且状态码为 400</p>
<div class="theme-admonition theme-admonition-tip admonition_lhEC alert alert--success"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><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_c29J"></p>
<div class="theme-admonition theme-admonition-note admonition_lhEC alert alert--secondary"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><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_vMh4" 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_vMh4" 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_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></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_vMh4" 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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="nextjs" term="nextjs"/>
        <category label="honojs" term="honojs"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[记 · 在 AI 公司入职一个月的体验与感悟]]></title>
        <id>https://kuizuo.me/blog/experience-of-an-ai-company</id>
        <link href="https://kuizuo.me/blog/experience-of-an-ai-company"/>
        <updated>2024-06-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[这篇文章分享了作者在一家 AI 公司入职一个月的心得和体会，包括工作中的挑战与成长。]]></summary>
        <content type="html"><![CDATA[<div class="theme-admonition theme-admonition-success admonition_lhEC alert alert--success"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p>作者已于 9 月 30 日离职，以下内容只发生于入职后的 1 个月，之后就过着如同牛马般的生活，日子又开始没有盼头了😔。</p></div></div>
<p>已经在一家 AI 公司入职了一个月，对坐班有些厌恶的我，没想到有一天也会开始通勤打卡。而经历了这一个月的工作，我对坐班的态度有所转变，开始理解这种工作方式对我的意义。是时候分享入职这期间的工作内容与感受。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<p>初入职场，初次面试就这种情况，说真的我当时都有点心灰意冷了，我猜想是不是因为有其他合适的人选，于是就不招我了，就连信息也不给我打一个招呼，相当于把我拉黑似得。随后我就到附近的麦当劳花了 10 元的套餐安慰了一下自己，麦！</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<p>听完之后是不是莫名的感觉这个 offer 拿的好像有点莫名其妙的感觉😂，不管怎么样结果是好的就行了。</p>
<p>不过拿到 offer 后，我并没有选择马上入职，经历了一次被鸽的经历，对该公司的印象带有一些怀疑。其次就是这是一家初创 AI 公司，规模不大，从应届生找工作的角度，第一份正式的工作的起点很关键，如果能直接进大厂，后续跳槽到其他公司大概率也不成问题。</p>
<p>但在当地我投递了 20 多家已读不回的情况下，加上这份已有的 offer 不等人（急招），加上我家里人给我推荐的工作内容我并不是很满意，于是思考了两天，最终还是选择入职了这家公司。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></p>
<p>几天后，公司来了一个阿里做 B 端低代码开发的同事，也是负责前端开发，这不，我可以间接和这个老哥那学习大厂相关经验，我还正愁着没大厂相关的经验😄。</p>
<p>我询问他来这家公司的原因，他说被裁了，在家接外包一年了，不稳定就准备找工作，恰好这家公司急招，于是就来了。</p>
<div class="theme-admonition theme-admonition-warning admonition_lhEC alert alert--warning"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p>端午节后，这位老哥提离职了，原因的话我就不具体说了，可能是因为年龄大了，不适合坐班了。虽然早有预感，但还是有点不舍。因为现在部门的前端重任都在我这了😭</p></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></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_c29J"></p>
<p>目前我已经能感受到最大的福利就是那个 magicui 动效库的模版，当然了，这个是要给公司的官网用上的，我也是蹭公司的福，给自己的站点用上了这个动效库。</p>
<p>此外像节日福利，如这次端午节，就是聚餐和发粽子，这也就没什么好说的。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></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_vMh4" 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_c29J"></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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="AI" term="AI"/>
        <category label="工作" term="工作"/>
        <category label="记录" term="记录"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[React Native 开发心得分享]]></title>
        <id>https://kuizuo.me/blog/react-native-develop-experience</id>
        <link href="https://kuizuo.me/blog/react-native-develop-experience"/>
        <updated>2024-05-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近研究了一下 React Native(简称RN)，并用它作为毕设项目（一个仿小红书的校园社交应用）。经过一段时间的折腾，对 RN 生态有了一些了解，是时候可以分享一些心得了。]]></summary>
        <content type="html"><![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_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></p>
<p>这点对于开发体验而言并不友好。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_lhEC alert alert--danger"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><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_vMh4" 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_vMh4" 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_c29J"></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_c29J"></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_c29J"></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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></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_c29J"></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_c29J"></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_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></p>
<p>Expo <a href="https://docs.expo.dev/router/advanced/tabs/" target="_blank" rel="noopener noreferrer">自带案例</a>，实现效果也简单，这里不在赘述了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></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_vMh4" 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_c29J"></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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="react native" term="react native"/>
        <category label="原生" term="原生"/>
        <category label="心得分享" term="心得分享"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[有了 Prisma 就别用 TypeORM 了]]></title>
        <id>https://kuizuo.me/blog/with-prisma-dont-use-typeorm</id>
        <link href="https://kuizuo.me/blog/with-prisma-dont-use-typeorm"/>
        <updated>2024-01-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[要说 2024 年 Node.js 的 ORM 框架应该选择哪个？毫无疑问选 Prisma。至于为何，请听我细细道来。]]></summary>
        <content type="html"><![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_vMh4" 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_vMh4" 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_c29J"></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_c29J"></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_vMh4" 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_c29J"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></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_vMh4" 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_vMh4" 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_c29J"></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_c29J"></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_c29J"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165658-Untitled%207.png" alt="Untitled" class="img_c29J"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/2024/0113165658-Untitled%208.png" alt="Untitled" class="img_c29J"></p>
<p>这无疑会诱发一些潜在 bug，我就多次因为要 select 某表中的某个字段，却因拼写错误导致查询失败。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_lhEC alert alert--success"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><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_vMh4" 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_c29J"></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_c29J"></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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="orm" term="orm"/>
        <category label="prisma" term="prisma"/>
        <category label="typeorm" term="typeorm"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[2023 · 谈谈职业规划]]></title>
        <id>https://kuizuo.me/blog/2023-year-end-summary</id>
        <link href="https://kuizuo.me/blog/2023-year-end-summary"/>
        <updated>2023-12-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[又到了年底写年终总结的时候了，说实话今年感觉没什么内容可写。上半年发生了比较多的事不方便叙述，而下半年我忙于学校课程 + 课程重修，过得其实还有点浑浑噩噩。]]></summary>
        <content type="html"><![CDATA[<p>又到了年底写年终总结的时候了，说实话今年感觉没什么内容可写。上半年发生了比较多的事不方便叙述，而下半年我忙于学校课程 + 课程重修，过得其实还有点浑浑噩噩。</p>
<p>不过如今都大四了，也确实是要考虑实习的事了。我想结合我自身情况，谈谈我是怎么看待工作或者往远点说职业规划方面的想法。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></p>
<p>此外，我还需要重修当初因休学而没去考试的几门课程（还挺多的，重修费还交了我不少😢），导致我最后一场考试时间是在 12月中旬。还是强调那一点，我不希望工作与学业这两者同时进行。关于这学校的诸多不满，待我毕业后我一定要说说，感受什么叫中国的私立大学。</p>
<p>此外还有一点，也是我不想提及的一点，这段期间我是处于“监视”状态，内心总悬着一个不知何时爆炸的炸弹，生怕突然爆炸将会打乱我的行程。</p>
<p>好在如今期限已到，心里悬着的一块石头终于放下来了，如释重负。</p>
<p>于是在上述的因素下，今年下半年我就不想找实习了。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_lhEC alert alert--success"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p><a href="https://kuizuo.me/blog/experience-of-an-ai-company">记 · 在 AI 公司入职一个月的体验与感悟</a></p></div></div>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></p>
<p>总之持之以恒在我这还不存在过，<strong>多数的开始是因一腔热血，而最后结束时却草草了事</strong>。</p>
<p>我想是是过度的自由造就了这般现状，想要解决这个问题，坐班可能还真可以。</p>
<hr>
<p>或许是因为比同龄人提前踏入过社会，有过几段工作经验，所以在就业形势严峻的时代，其实我反倒不是那么慌张。</p>
<p>何况目前经济状态也还算正常，哪怕不找工作也足够养活我自己好一整子。至少相比同龄人下，我已经挺富裕了。也没必要搞个打赏在自己的网站或项目上放置收款二维码等无关项目信息。对我而言，编写这些的目的不是为了接某个单，赚个打赏费，更多是一时兴起，分享给有需要的人，希望让其更为纯粹一些，仅此而已。</p>
<p>我对赚钱的态度也很简单，悦己便可（做点让自己愉悦的事情），数额能够维持自身生存便足够了。不过现在我还没开始到还贷的境地，或许只有压迫感来临时，才会让我激起对金钱的渴望，改变对金钱的态度。</p>
<p><strong>取悦自己，生活最好的心态。</strong></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="年终总结" term="年终总结"/>
        <category label="工作" term="工作"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于 restful api 路径定义的思考]]></title>
        <id>https://kuizuo.me/blog/restful-api-url-definition</id>
        <link href="https://kuizuo.me/blog/restful-api-url-definition"/>
        <updated>2023-11-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[关于 restful api 想必不用多说，已经有很多文章都阐述过它的设计原则，但遵循这个原则可以让你的 API 接口更加规范吗？以下是我对 restful api 风格的一些思考🤔。]]></summary>
        <content type="html"><![CDATA[<p>关于 restful api 想必不用多说，已经有很多文章都阐述过它的设计原则，但遵循这个原则可以让你的 API 接口更加规范吗？以下是我对 restful api 风格的一些思考🤔。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_lhEC alert alert--warning"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="杂谈" term="杂谈"/>
        <category label="restful" term="restful"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[一位未曾涉足算法的初学者收获]]></title>
        <id>https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte</id>
        <link href="https://kuizuo.me/blog/discoveries-of-an-algorithm-neophyte"/>
        <updated>2023-09-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[正如标题所言，在我四年的编程经历中就没刷过一道算法题，这可能与我所编写的应用有关，算法对我而言提升不是特别大。加上我几乎都是在需求中学习，而非系统性的学习。所以像算法这种基础知识我自然就不是很熟悉。]]></summary>
        <content type="html"><![CDATA[<p>正如标题所言，在我四年的编程经历中就没刷过一道算法题，这可能与我所编写的应用有关，算法对我而言提升不是特别大。加上我几乎都是<strong>在需求中学习，而非系统性的学习</strong>。所以像算法这种基础知识我自然就不是很熟悉。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></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_c29J"></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_c29J"></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_c29J"></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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="算法" term="算法"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Nest grpc 实践之调用 python ddddocr 库]]></title>
        <id>https://kuizuo.me/blog/nest-grpc-ocr</id>
        <link href="https://kuizuo.me/blog/nest-grpc-ocr"/>
        <updated>2023-07-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将使用 nest 通过 grpc 的方式来调用 python 的 ddddocr 库来识别验证码。]]></summary>
        <content type="html"><![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_vMh4" 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_c29J"></p>
<p>本文源码 <a href="https://github.com/kuizuo/nest-ocr" target="_blank" rel="noopener noreferrer">nest-ocr</a></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<p>导入完毕后将显示如下页面</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823589.png" alt="Untitled" class="img_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></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_c29J"></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_c29J"></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_c29J"></p>
<p>到这里我们就已经搞定了如何调用 grpc 服务，接下来就要自己去实现标题的需求。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></p>
<p>用户想要调用 ddddocr 库，最理想的肯定是让用户直接和 python 打交道，但应用（这里指 Web）通常不会使用 python 进行编写，而其他语言(js)想要跨语言调用，这时 rpc 就再适合不过了。</p>
<p>可能会有人说这么操作多此一举，我只能说根据性能和业务为主。相比将 nest 后端服务迁移到 python 上，和在 nest 与 python 之间多层 grpc，在两者的工作量之下我肯定毫不疑问的选择后者。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></p>
<p>用户以 http 方式访问的效果。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307290823595.png" alt="Untitled" class="img_c29J"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="nest" term="nest"/>
        <category label="grpc" term="grpc"/>
        <category label="python" term="python"/>
        <category label="ddddocr" term="ddddocr"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[nest.js 添加 swagger 响应数据文档]]></title>
        <id>https://kuizuo.me/blog/nest-swagger-response-data</id>
        <link href="https://kuizuo.me/blog/nest-swagger-response-data"/>
        <updated>2023-07-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[nest.js 添加 swagger 响应数据文档]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></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_c29J"></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_vMh4" 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_c29J"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307180224284.png" alt="image-20230718022446188" class="img_c29J"></p>]]></content>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="nest" term="nest"/>
        <category label="swagger" term="swagger"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[叙一名转专业+休学的大学生经历]]></title>
        <id>https://kuizuo.me/blog/narrate-a-college-student</id>
        <link href="https://kuizuo.me/blog/narrate-a-college-student"/>
        <updated>2023-07-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[我一般很少做年中总结，但是这上半年发生在我事情比较多，加上毕业季，万千感慨涌上心头。]]></summary>
        <content type="html"><![CDATA[<p>我一般很少做年中总结，但是这上半年发生在我事情比较多，加上毕业季，万千感慨涌上心头。</p>
<p>过得很快，本该在这个时间段毕业的我，因一意孤行申请休学一年，导致我比原同一届的人晚毕业一年。也正是这个决定改变了我的人生轨迹，让我成长了许多。</p>
<p>如今的我作为一名准大四的大学生 👨‍🎓，且经历过转专业和休学的大学生，来叙述自己的经历。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_lhEC alert alert--info"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_vMh4" 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_vMh4" 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_lhEC alert alert--warning"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p>最后我想告诫一些有想转专业的同学，如果你能接受转专业的麻烦，并且真的认为转专业对你带来帮助，那可以转。但如果只是为了换个班级换个室友什么的，转后的代价或许比你与同学间不友好相处四年还要负重。<strong>总之，转与不转，最终目的是以最快捷轻松的方式拿毕业证为主。</strong></p></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></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_c29J"></p>
<p>现在回想，当时动不动就加班到半夜，但是我对此没有怨言，甚至很享受这段时间。做自己愿意做的事情，我认为这就足够有意义了。不过这段时间持续不长，主要开发阶段也就是上半年的事情，而下半年由于本地疫情原因加上我身处老家，迟迟未能回到工位。而平常更多的任务是维护，兴致也没有一开始从 0 到 1 的热情。要知道维护是件非常枯燥且重复的事情，运营人员一有问题，就反馈到我这边，而当时写的项目又是协议项目，基本上每隔一阵子，我就要重新抓包，重新分析参数，重新部署。加上当时易语言的项目又没有 CI/CD，于是我维护完之后，就又要将易语言的项目丢到这些服务器上，我当时非常想写一个脚本，奈何我们的做单服务器基本上隔一段时间换一批，而这些服务器又是物理机（一台真实的远程机子），而非腾讯云、阿里云这种服务器，更没有批量备份，系统镜像这一功能。因此在安装环境和部署上可以说花费了我很多没必要的时间。（也可以说因此契机我彻彻底底的放弃了易语言，对于一个大型应用而言，它有数不清的缺点）</p>
<p>不过最终我还是复学了，因为在这个项目上，我只看到不断重复的日子，且项目并不是一个长久项目，在未来的某个时刻必定会被制裁（只是我没想到会这么快），最终随着一年的休学期限到来，在做了项目移交后，我便退出了该团队。在此期间自然挣了点小钱（不多，也就相当于阿里 P6 级别薪酬），足够我快活潇洒好些年了。</p>
<p>可好景不长，往往一个人在巅峰后的一段时间，必然要经历一次落差。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_lhEC alert alert--info"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p>你可能会比较好奇，为何不边上学边工作，而是要休学。我当时有两种想法，一种前面也说了，就是抱着辍学的心态，这里也就不在赘述。另一种就和我个人做事比较有关了，我做任何一件事情的前提就是不希望有其他事物来打扰，尤其是学校的因素，我学校是比较极端，规定的特别死，你想要实习只能在大四这个阶段(并且大四上还得上半个学期的课)，需要将课程修完才可。因此就有了休学。</p></div></div>
<p><strong>如果仅仅是转专业+休学的话，<code>其实</code>就没什么好看的了，不过需要这些的铺垫才有后面一言难尽的故事。</strong></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<div class="theme-admonition theme-admonition-note admonition_lhEC alert alert--secondary"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><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_c29J"></p>
<p>至此，事实告诉我白白浪费了一整年的时间，并且此后还将给我带来了诸多麻烦。</p>
<p>但如今我回想这个事件，我都已经退出这个团伙近半年了，已经金盆洗手了，却依旧有所关联。👮‍♂️ 可不管你的解释，只要你参与了，获利了，势必要追究下来。如果我当时能够劝阻他们不做，或许如今就没有这么多麻烦事，可这仅存在于如果。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307110122812.png" alt="Untitled" class="img_c29J"></p>
<p>回到这个项目而言，本质上确实不是很正规很传统，但也不至于黑产那种，更多的称呼是灰色产业。👮‍♂️ 给定性为 “<strong>提供侵入、非法控制计算机信息系统程序、工具罪”</strong>，但事实上这个程序并没有非法侵入服务器，我只是将用户的正常操作转变为电脑程序操作，将正常的数据包给模拟出来，可以省去人为操作的一个过程，而不是入侵对方的服务器，通过数据库的方式直接修改。在笔录期间，我还特意举例了游戏外挂和游戏脚本的区别，前者是实实在在的篡改数据，后者则是程序来模拟人为的情况，而我所做的部分就是游戏脚本的部分，根本不能算作非法控制。但 👮‍♂️ 不会按照我的理解，更别说检察院了，他们都只会认为这是在”破坏公平性“，那么就归属同一“恶劣”性质，就可以归属这个罪。总之我说的再多，说的再好，也都不及别人的一句反对。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<p>至于为何要这么说，我想有很大原因是因为学生的负面因素会影响学校的名誉（挣钱），我想读过大学的人应该很清楚。但在谈话的期间，我表明了我是在休学期间做的事情，只是在校读书期间被叫去了（在校期间都没接触这项目），并且 👮‍♂️ 是没有直接通过校方来找我（我想是 👮‍♂️ 也不想麻烦学校），可以这么说，如果当地的 👮‍♂️ 与我校老师没有交识，校方可能都不知道我这个情况。但无论我如何解释，校方还是会以你是这个学校的学生身份来对你进行处理。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202307110122813.png" alt="Untitled" class="img_c29J"></p>
<p>当然，我可以比较肯定的是，辅导员也不希望我出事，毕竟现在大学生如果出事，辅导员也是要背点锅的，但院长的话我就不清楚了，但从与他的谈话中，总感觉他不见得我好的样子。</p>
<p>不过要说学生身份是否对我有利的话，那答案是肯定有的。比如说开具学生在校证明，酌情处理等等。但不是说大学生就是免死金牌，这要是换成未成年人，那还说不准真是。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="年终总结" term="年终总结"/>
        <category label="人生感悟" term="人生感悟"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[MacBook 体验有感]]></title>
        <id>https://kuizuo.me/blog/macbook-experience</id>
        <link href="https://kuizuo.me/blog/macbook-experience"/>
        <updated>2023-05-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[作者是一位从来没用过苹果产品的程序员，但在使用了一周的 MacBook Pro 14 寸后，便爱不释手。]]></summary>
        <content type="html"><![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_vMh4" 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_c29J"></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_c29J"></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_c29J"></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_c29J"></p>
<p>不过最终能让我购买的很大原因是在五一期间我实在不想用那破 window 本，因此第二天再次联系店员决定直接付款拿丐版现货。因为考虑两年后大概率也会更换电子设备（距离我上次更换电子设备也过去两年），所以综合考虑当前的配置在这两年应该是足以使用（这句话也许说的有点早）。</p>
<p>实体店与线上购买没有本质的区别，<strong>价格上都是一致的</strong>，也是有教育优惠的。只是人家会帮你激活设备，在你的眼皮子底下看看有无问题，确认无误后，交钱走人便可。此外可能还会赠送一些额外的一些配件，如键盘膜，屏幕膜，清洁套装，拓展坞（没有手提包），不过这些对我来说都非刚需，只要 MacBook Pro 没问题即可。</p>
<p>不过这里想要提一下，人家是挺极力推荐我购买 apple care 的（顺带有个配件券），据我了解，貌似是有一个 apple care 的指标，需要达到多少这样。不过我个人不喜欢买保险，因此便没有购买。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></p>
<p>这与我一些不好的使用习惯还是有关的，我通常会打开好几个标签页，好几个 应用而不关闭，这就导致内存时常处于高负载状态下，不断的从硬盘中拿内存，这样不仅伤硬盘同时也伤电池。此外我通常会用 Type-c 一线连接外接显示器（既可以充电又可以显示），加上我没有拔电以及关机的习惯(可能要过好几周才会重关一次)，这就导致 8 个月的时间内电池最大容量缩减。</p>
<p>现在来看或许当初买个 apple care 还能免费换个电池似乎还划得来。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<p>如果你有桌面游戏的需求，建议拉黑 mac。此外还有一些 window 的专业软件，你在 mac 上可能找不到与之对应或平替的软件，尤其是在大学课程中，老师几乎不可能给学生发个 dmg 文件，如果你在大学期间买 mac，又要兼顾学校的课程软件需求，又不得不安装 window 虚拟机，与其如此折腾不如一开始就选用相对便宜的 win 本，还能减少一些经济压力。不过我觉得大学老师上课所说的一些软件都没必要安装，反而占用一些不必要的空间，（vc++、eclipse 等等），如果你们老师提到了 Vscode 那当我没说。</p>
<p>但出色的系统、高素质的屏幕，注定能让 MacBook 能够成为某部分群体的生产力工具，挣钱的机器。选用 macbook 的用户想必都希望在它任职期间产生数十倍的价值，当然排除我这个买来尝鲜的。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/202305050428900.png" alt="Untitled" class="img_c29J"></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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="macOS" term="macOS"/>
        <category label="MacBook" term="MacBook"/>
        <category label="记录" term="记录"/>
        <category label="使用体验" term="使用体验"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[距离我上篇笔记还是在?]]></title>
        <id>https://kuizuo.me/blog/why-i-dont-write-notes</id>
        <link href="https://kuizuo.me/blog/why-i-dont-write-notes"/>
        <updated>2023-03-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[当我起这个标题时，其实我已经很久没更新（翻看）过笔记了，甚至我都不记得我的博客还有笔记这个东西。]]></summary>
        <content type="html"><![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_c29J"></p>
<p>然而并不是我的技术栈没更新，而是我实实在在没去为这些技术栈编写过笔记。仅有的只是博文来记录自己所用的过程。</p>
<p>因此我想思考下我为何不记录笔记。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="杂谈" term="杂谈"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Typescript 全栈最值得学习的技术栈 TRPC]]></title>
        <id>https://kuizuo.me/blog/typescript-full-stack-technology-trpc</id>
        <link href="https://kuizuo.me/blog/typescript-full-stack-technology-trpc"/>
        <updated>2023-03-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文介绍了 tRPC 技术以及它与传统 RESTful API 的区别。同时 tRPC 可以帮助人们更快地开发全栈 TypeScript 应用程序，同时无需传统的 API 层，并保证应用程序在快速迭代时的稳定性。]]></summary>
        <content type="html"><![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_vMh4" 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_c29J"></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_c29J"></p>
<p>但这样为了更好的类型提示，无形之间又增加了工作量，我需要定义每个接口的 Response 与 Body 类型，就极易造成开发疲惫，不愿维护代码。而本次所要介绍的技术栈 tRPC 就能够帮你省去重复的类型定义的一个 web 全栈框架。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></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_vMh4" 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_c29J"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/qicvoGjshx.png" alt="" class="img_c29J"></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_c29J"></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_c29J"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/cvlDJjhwPl.png" alt="" class="img_c29J"></p>
<p>分别输出三者 result 也没有任何问题。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/hbL8So_RzB.png" alt="" class="img_c29J"></p>
<p>这是 tRPC 的一个特性：<strong>请求批处理，将同时发出的请求（调用）可以自动组合成一个请求。</strong></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_lhEC alert alert--danger"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><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_c29J"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/RTdWJn_55p.png" alt="" class="img_c29J"></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_vMh4" 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_vMh4" 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_c29J"></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_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_LAR2 alert alert--info details_doiK" data-collapsed="true"><summary>schema.prisma</summary><div><div class="collapsibleContent_j1rY"><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_c29J"></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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></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_c29J"></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_vMh4" 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_lhEC alert alert--warning"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><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_c29J"></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_vMh4" 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_c29J"></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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="trpc" term="trpc"/>
        <category label="next" term="next"/>
        <category label="prisma" term="prisma"/>
        <category label="zod" term="zod"/>
        <category label="auth.js" term="auth.js"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[将 Supabase 作为下一个后端服务]]></title>
        <id>https://kuizuo.me/blog/use-supabase-as-backend-service</id>
        <link href="https://kuizuo.me/blog/use-supabase-as-backend-service"/>
        <updated>2023-02-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文介绍了如何使用 Supabase 作为后端服务，使开发人员可以更快地构建和部署应用程序，无需配置数据库或编写复杂的身份验证代码。将使用 Nuxt.js 和 Supabase，以实现一个图床网站为例，来演示如何在前端中使用 Supabase API 和 Storage 服务。]]></summary>
        <content type="html"><![CDATA[<p>对于想快速实现一个产品而言，如果使用传统开发，又要兼顾前端开发，同时又要花费时间构建后端服务。然而有这么一个平台（Baas Backend as a service）后端即服务，能够让开发人员可以专注于前端开发，而无需花费大量时间和精力来构建和维护后端基础设施。</p>
<p>对于只会前端的人来说，这是一个非常好的选择。后端即服务的平台使得开发人员能够快速构建应用程序，更快地将其推向市场。当然了，你可以将你的后端应用接入 Baas，这样你就无需配置数据库，编写复杂的身份效验。</p>
<p>如果你想了解 Baas，我想这篇文章或许对你有所帮助。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></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_c29J"></p>
<p>为该项目起名，设置数据库密码，以及分配地区。</p>
<div class="theme-admonition theme-admonition-warning admonition_lhEC alert alert--warning"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><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_c29J"></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_c29J"></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_vMh4" 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_c29J"></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_c29J"></p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_MLm6_i1Pb-.png" alt="" class="img_c29J"></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_c29J"></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_c29J"></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_c29J"></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_c29J"></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_c29J"></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_c29J"></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_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></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_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></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_c29J"></p>
<p><img decoding="async" loading="lazy" src="https://azlbliyjwcemojkwazto.supabase.co/storage/v1/object/public/public/image_jyaUMSDed2.png" alt="" class="img_c29J"></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_c29J"></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_c29J"></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_vMh4" 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_c29J"></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_c29J"></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_lhEC alert alert--success"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p>本文中的所有图片数据都来源于 supabase bucket。</p></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_c29J"></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_vMh4" 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_vMh4" 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_c29J"></p>
<p>说实话，对于个人独立开发者的项目都绰绰有余了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="supabase" term="supabase"/>
        <category label="nuxt" term="nuxt"/>
        <category label="project" term="project"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[🍋 使用 Fresh 框架构建Web 应用]]></title>
        <id>https://kuizuo.me/blog/use-fresh-build-web-applicatioin</id>
        <link href="https://kuizuo.me/blog/use-fresh-build-web-applicatioin"/>
        <updated>2023-02-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[使用 Fresh 框架构建Web 应用，用于将链接转换为卡片样式的预览效果图。]]></summary>
        <content type="html"><![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_vMh4" 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_lhEC alert alert--warning"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_vMh4" 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_c29J"></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_vMh4" 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_vMh4" 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_c29J"></p>
<p>不过既然服务端组件也有很多限制，就比如说服务端状态下，是无法使用 Web 相关 Api 的，数据传输（通过 props）是有前提的，要 JSON 可序列化，也就是说只能传递基本类型、基本对象、数组，像 Date，自定义类，函数等复制对象是无法传递的。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_vMh4" 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_c29J"></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_vMh4" 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>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="deno" term="deno"/>
        <category label="fresh" term="fresh"/>
        <category label="web" term="web"/>
        <category label="project" term="project"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Deno 不只是个 Javascript 运行时]]></title>
        <id>https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime</id>
        <link href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime"/>
        <updated>2023-01-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Deno 是一个安全的 JavaScript 和 TypeScript 运行时，作者是 Ryan Dahl（也是 Node.js 的原作者）。Deno 的诞生之初是为了解决 2009 年首次设计 Node.js 时的一些疏忽。我认为这种改造动机很有道理，因为我相信每个程序员都希望有机会能重写他们已有 10 年历史的代码。]]></summary>
        <content type="html"><![CDATA[<p>Deno 是一个安全的 JavaScript 和 TypeScript 运行时，作者是 Ryan Dahl（也是 Node.js 的原作者）。Deno 的诞生之初是为了<a href="https://link.juejin.cn/?target=https://www.youtube.com/watch?v=M3BM9TB-8yA" target="_blank" rel="noopener noreferrer">解决 2009 年首次设计 Node.js 时的一些疏忽</a>。我认为这种改造动机很有道理，因为<strong>我相信每个程序员都希望有机会能重写他们已有 10 年历史的代码。</strong></p>
<p>deno 刚出的时候就听闻了，传言 deno 是下一代 node.js。不过如今看来，还革不了 node.js 的命。如果要说两者字面上的区别，Deno 的来源是 Node 的字母重新组合（Node = no + de），表示"拆除 Node.js"（de = destroy, no = Node.js）。</p>
<p>趁着假期学了一段时间的 deno（指<a href="https://deno.land/manual@v1.29.3/introduction" target="_blank" rel="noopener noreferrer" title="文档">文档</a>刷了一遍），想分享本人作为 node 开发者在学习 deno 时认为的一些亮点，以及个人对 deno 与 node 见解。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="开发环境">开发环境<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83" class="hash-link" aria-label="开发环境的直接链接" title="开发环境的直接链接">​</a></h3>
<p><a href="https://deno.land/manual@v1.29.2/getting_started/installation" target="_blank" rel="noopener noreferrer" title="Installation | Manual | Deno">Installation | Manual | Deno</a></p>
<p>默认情况下 deno 会根据不同的系统，选择相应的安装目录，以及依赖目录，你可以<a href="https://deno.land/manual@v1.29.3/getting_started/setup_your_environment#environment-variables" target="_blank" rel="noopener noreferrer" title="配置环境变量">配置环境变量</a>来改变 deno 的默认行为。</p>
<p>这里我选用 vscode 进行开发，安装<a href="https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno" target="_blank" rel="noopener noreferrer" title="deno官方插件">deno 官方插件</a>。此时创建一个项目工程文件夹，打开 vscode，并创建 <code>.vscode/settings.json</code> 内容如下</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="codeBlockTitle_P25_"><span></span>.vscode/settings.json<span style="flex:1;text-align:right">json</span></div><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%)">"deno.enable"</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 property" style="color:hsl(5, 74%, 59%)">"deno.lint"</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 property" style="color:hsl(5, 74%, 59%)">"editor.formatOnSave"</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 property" style="color:hsl(5, 74%, 59%)">"[typescript]"</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><span class="token property" style="color:hsl(5, 74%, 59%)">"editor.defaultFormatter"</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%)">"denoland.vscode-deno"</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>
<p>在 vscode 中默认会将 ts 代码认为是 node 运行时环境，因此需要在项目工程下手动配置并启用 deno，让 vscode 以 deno 运行时环境来语法解析 ts 代码。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="deno-的一些亮点">deno 的一些亮点💡<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#deno-%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BA%AE%E7%82%B9" class="hash-link" aria-label="deno 的一些亮点💡的直接链接" title="deno 的一些亮点💡的直接链接">​</a></h2>
<p>因为 deno 与 node 一样，都是 javascript 运行时（deno 合理来说是 typescript 运行时）。所以在 javascript 的部分就没什么好说的了，主要对比 deno 相比与 node 的优势，或说我个人觉得一些使用亮点。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="官方所介绍的亮点">官方所介绍的亮点<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#%E5%AE%98%E6%96%B9%E6%89%80%E4%BB%8B%E7%BB%8D%E7%9A%84%E4%BA%AE%E7%82%B9" class="hash-link" aria-label="官方所介绍的亮点的直接链接" title="官方所介绍的亮点的直接链接">​</a></h3>
<p>以下是官方所介绍的<a href="https://deno.land/manual@v1.29.3/introduction#feature-highlights" target="_blank" rel="noopener noreferrer" title="亮点">亮点</a>，我对其做了翻译</p>
<ul>
<li>
<p>提供<a href="https://deno.land/manual@v1.29.3/runtime/web_platform_apis" target="_blank" rel="noopener noreferrer" title="web平台功能">web 平台功能</a>，采用网络平台标准。例如，使用 ES 模块、Web worker 和支持 <code>fetch()</code>。</p>
</li>
<li>
<p>默认安全。除非显式启用，否则无法访问文件、网络或环境。</p>
</li>
<li>
<p>支持开箱即用的 <a href="https://deno.land/manual@v1.29.3/advanced/typescript" target="_blank" rel="noopener noreferrer" title="TypeScript">TypeScript</a>。</p>
</li>
<li>
<p>提供单个可执行文件 （<code>deno</code>）。</p>
</li>
<li>
<p>为编辑器提供内置的开发工具，如代码格式化程序 （<a href="https://deno.land/manual@v1.29.3/tools/formatter" target="_blank" rel="noopener noreferrer" title="deno fmt">deno fmt</a>）、linter （<a href="https://deno.land/manual@v1.29.3/tools/linter" target="_blank" rel="noopener noreferrer" title="deno lint">deno lint</a>）、测试运行程序（<a href="https://deno.land/manual@v1.29.3/basics/testing" target="_blank" rel="noopener noreferrer" title="deno test">deno test</a>）和<a href="https://deno.land/manual@v1.29.3/getting_started/setup_your_environment.md#using-an-editoride" target="_blank" rel="noopener noreferrer" title="语言服务器">语言服务器</a>。</p>
</li>
<li>
<p>拥有<a href="https://deno.land/std@0.172.0" target="_blank" rel="noopener noreferrer" title="一组经过审查（审核）的标准模块">一组经过审查（审核）的标准模块</a>，保证与 Deno 一起使用。</p>
</li>
<li>
<p>可以将脚本<a href="https://deno.land/manual@v1.29.3/tools/bundler" target="_blank" rel="noopener noreferrer" title="捆绑">捆绑</a>到单个 JavaScript 文件或<a href="https://deno.land/manual@v1.29.3/tools/compiler" target="_blank" rel="noopener noreferrer" title="可执行文件">可执行文件</a>中。</p>
</li>
<li>
<p>支持使用现有的 npm 模块</p>
</li>
</ul>
<p>以下会针对部分亮点，进行个人的见解。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="自带实用工具">自带实用工具<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#%E8%87%AA%E5%B8%A6%E5%AE%9E%E7%94%A8%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="自带实用工具的直接链接" title="自带实用工具的直接链接">​</a></h3>
<p>deno 则是自带代码格式化（<code>deno fmt</code>）、代码风格（<code>deno lint</code>）、代码测试（<code>deno test</code>）、依赖检查器（<code>deno info</code>）等等的功能。而这些在 node 中，你需要通过第三方的库，如 eslint，jest 才能实现。</p>
<p>你可以在项目工程中添加配置文件 <a href="https://deno.land/manual@v1.29.2/getting_started/configuration_file" target="_blank" rel="noopener noreferrer" title="deno.json">deno.json</a>来定制化代码风格（rust 中也有类似的功能），但在 node 中必须要借助第三方的库，或是 IDE 才能实现。</p>
<p>不过也能理解，在当时的编程环境背景下，javascript 还主要作为前端的脚本语言使用，又怎能让 node 来做相关规范呢？（这句话可能有点不妥）</p>
<p><strong>这点我认为对开发者是否选用你这门语言的一个加分项</strong>，并且这些功能也应该作为编程语言所自带的，有官方的背书（保证），对代码风格才更有所保障。</p>
<p>这里有份 <a href="https://deno.land/manual@v1.29.4/references/cheatsheet#nodejs---deno-cheatsheet" target="_blank" rel="noopener noreferrer" title="官方小抄">官方小抄</a> 可以知道通过<code>deno xxx</code>等命令能够做到 node 原本需要通过第三方库才能实现的功能。</p>
<table><thead><tr><th>Node.js</th><th>Deno</th></tr></thead><tbody><tr><td><code>node file.js</code></td><td><code>deno run file.js</code></td></tr><tr><td><code>ts-node file.ts</code></td><td><code>deno run file.ts</code></td></tr><tr><td><code>npm i -g</code></td><td><code>deno install</code></td></tr><tr><td><code>npm i</code> / <code>npm install</code></td><td><em>n/a</em></td></tr><tr><td><code>npm run</code></td><td><code>deno task</code></td></tr><tr><td><code>eslint</code></td><td><code>deno lint</code></td></tr><tr><td><code>prettier</code></td><td><code>deno fmt</code></td></tr><tr><td><code>rollup</code> / <code>webpack</code> / etc</td><td><code>deno bundle</code></td></tr><tr><td><code>package.json</code></td><td><code>deno.json</code> / <code>deno.jsonc</code> / <code>import_map.json</code></td></tr><tr><td><code>tsc</code></td><td><code>deno check</code></td></tr><tr><td><code>typedoc</code></td><td><code>deno doc</code></td></tr><tr><td><code>jest</code> / <code>ava</code> / <code>mocha</code> / <code>tap</code> / etc</td><td><code>deno test</code></td></tr><tr><td><code>nodemon</code></td><td><code>deno run/lint/test --watch</code></td></tr><tr><td><code>nexe</code> / <code>pkg</code></td><td><code>deno compile</code></td></tr><tr><td><code>npm explain</code></td><td><code>deno info</code></td></tr><tr><td><code>nvm</code> / <code>n</code> / <code>fnm</code></td><td><code>deno upgrade</code></td></tr><tr><td><code>tsserver</code></td><td><code>deno lsp</code></td></tr><tr><td><code>nyc</code> / <code>c8</code> / <code>istanbul</code></td><td><code>deno coverage</code></td></tr><tr><td><code>benchmarks</code></td><td><code>deno bench</code></td></tr></tbody></table>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="远程导入"><a href="https://deno.land/manual@v1.29.3/basics/modules#remote-import" target="_blank" rel="noopener noreferrer" title="远程导入">远程导入</a><a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#%E8%BF%9C%E7%A8%8B%E5%AF%BC%E5%85%A5" class="hash-link" aria-label="远程导入的直接链接" title="远程导入的直接链接">​</a></h3>
<p>与 node 不同，使用 node 通常需要从 npm 官方包来下载并导，有 npm 这样的包管理器来统一管理这些包（package），我们通常称这种为中心化，而 deno 与 go 的做法很像，你可以将你的封装好的代码定义成一个包，并将其放在任何网络可访问的地方，比如 github，或是私有地址，然后通过网络读取文件的方式来导入，这种称为去中心化。</p>
<div class="theme-admonition theme-admonition-tip admonition_lhEC alert alert--success"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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>node 也不一定要用 npm 来下载模块，也可以本地模块或者私有模块。</div></div>
<p>关于中心化与去中心化管理，各有优缺，这里不做细致讨论。</p>
<p>以下是 deno 官方远程导入的代码示例：</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>remote.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"> add</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> multiply </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%)">'https://x.nest.land/ramda@0.27.0/source/index.js'</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%)">totalCost</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">outbound</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"> inbound</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"> tax</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%)">number</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 function" style="color:hsl(221, 87%, 60%)">multiply</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token function" style="color:hsl(221, 87%, 60%)">add</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">outbound</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> inbound</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> tax</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span 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 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 function" style="color:hsl(221, 87%, 60%)">totalCost</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">19</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%)">31</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.2</span><span class="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 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 function" style="color:hsl(221, 87%, 60%)">totalCost</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">45</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%)">27</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.15</span><span class="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 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%)"> * Output</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><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token doc-comment comment" style="color:hsl(230, 4%, 64%)"> * 60</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%)"> * 82.8</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><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>https://x.nest.land/ramda@0.27.0/source/index.js</code> 可以替换成任何 ES module 特性（import/export）的模块。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="http-的方式运行代码">http 的方式运行代码<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#http-%E7%9A%84%E6%96%B9%E5%BC%8F%E8%BF%90%E8%A1%8C%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="http 的方式运行代码的直接链接" title="http 的方式运行代码的直接链接">​</a></h3>
<p>既然都能通过 http（cdn）远程导入模块，那远程运行文件自然也不成大问题。有时候像快捷体验一下别人的代码，或是想要在浏览器中运行一下代码，这时候就可以通过 http 的方式来运行代码。</p>
<p>这里我准备了一段代码，并部署到我的站点上，你可以通过如下命令得到该代码的执行结果（如果你有安装 deno 的话），放心这段代码并无危害，就是一段简单的 console.log 输出。</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 https://deno.kuizuo.me/main.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>在第一次使用时下载并缓存代码，你可以通过</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 info http://deno.kuizuo.me/main.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>来查看文件信息，如下</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/deb0_lGYRA.png" alt="" class="img_c29J"></p>
<p>deno info 还可以查看 deno 的相关配置，默认缓存都设置在 C 盘，你也可以设置<strong>DENO_DIR</strong> 环境变量来更改 deno 目录，可以到 <a href="https://deno.land/manual@v1.29.3/getting_started/setup_your_environment#environment-variables" target="_blank" rel="noopener noreferrer" title="Set Up Your Environment">Set Up Your Environment</a> 查看 deno 相关环境变量。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="依赖管理">依赖管理<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#%E4%BE%9D%E8%B5%96%E7%AE%A1%E7%90%86" class="hash-link" aria-label="依赖管理的直接链接" title="依赖管理的直接链接">​</a></h3>
<p>经常使用 node 的开发者应该对 node 的依赖感到无比厌烦，关于这部分强烈建议看 <a href="https://juejin.cn/post/6914508615969669127" target="_blank" rel="noopener noreferrer">node_modules 困境</a>，你就能知道 node 的 node_modules 设计的是有多少问题。看完你也就能知道为啥越来越多的 node 项目都使用 <a href="https://pnpm.io/" target="_blank" rel="noopener noreferrer">pnpm</a> 作为包管理。</p>
<p>虽然 node 有了 pnpm 包管理器这种情况会好一些，但本质在项目目录还是需要 node_modules 文件。也许你用过其他语言的包管理器，你会发现基本都是将所有用到的依赖全局缓存起来，当不同的项目工程需要用到依赖时，直接去全局缓存中找，而不是像 npm 一样，下载到项目工程目录下，存放在 node_modules 里。</p>
<p>而 deno 也是采用这种这种方式，<code>no npm install</code>，<code>no package.json</code>，<code>no node_modules/</code> ，<a href="https://deno.land/manual@v1.29.3/node/npm_specifiers#using-npm-packages-with-npm-specifiers" target="_blank" rel="noopener noreferrer" title="使用npm包">使用 npm 包</a>可以像下面这样，当你使用 deno run 时便会下载好依赖置全局缓存中。</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.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%)">// @deno-types="npm:@types/express@^4.17"</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-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"> express </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%)">'npm:express@^4.17'</span><span 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"> app </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%)">express</span><span class="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 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%)">'/'</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">req</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><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">  res</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">send</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'Hello World'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span 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 function" style="color:hsl(221, 87%, 60%)">listen</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">3000</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span 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 string" style="color:hsl(119, 34%, 47%)">'listening on http://localhost:3000/'</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>deno 刚发布的时候，甚至还不支持 NPM 软件包，这无非是要告诉用户 deno 社区没有轮子，要求用户自己去造一个。不过 deno 团队还是做出了比较正确的选择，支持 npm 软件包，并且还非常友好。</p>
<p>不过如果你在 deno 中使用了 npm 包，可能会存在一些兼容性问题，万一遇到了，也可以通过添加 <code>--node-modules-dir</code> 标识，在当前运行目录下创建 <code>node_modules</code> 文件夹。详见 <a href="https://deno.land/manual@v1.29.4/node/npm_specifiers#--node-modules-dir-flag" target="_blank" rel="noopener noreferrer" title="--node-modules-dir flag">--node-modules-dir flag</a></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="安全">安全<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#%E5%AE%89%E5%85%A8" class="hash-link" aria-label="安全的直接链接" title="安全的直接链接">​</a></h3>
<p><a href="https://deno.land/manual@v1.29.4/basics/permissions" target="_blank" rel="noopener noreferrer" title="Permissions">Permissions</a></p>
<p>在 2022 年 npm 出现过一些恶性的库，如 lodash-utils, faker.js, chalk-next。万一你不小心安装了上面，轻则项目无法运行，输出无意义乱码，重则删除本地文件。</p>
<p>又因为 npm 几乎没有代码审计的机制，任何开发者只需要有一个 npm 的账号就能在上面随意发布他想发布的包。通常来说电脑病毒都是通过随意读取与写入本地文件来达到病毒的目的，但在 deno 中，代码如果尝试写入与读入文件，都需要询问开发者是否允许操作。并且在 linux 系统，你可以指定像 /usr /etc 这样非 root 角色来操作该文件，避免真是病毒文件导致删除不该删除的文件。</p>
<p>此外像命令执行，网络访问，环境变量这些极易危害电脑的权限，deno 都会检测到，并做出提示告诫开发者是否允许执行。总之你能想到的电脑安全隐患，deno 都为你做好了。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="内置浏览器环境运行时">内置浏览器环境（运行时）<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#%E5%86%85%E7%BD%AE%E6%B5%8F%E8%A7%88%E5%99%A8%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E6%97%B6" class="hash-link" aria-label="内置浏览器环境（运行时）的直接链接" title="内置浏览器环境（运行时）的直接链接">​</a></h3>
<p>这是我认为 deno 最大的亮点。</p>
<p>总所周知，浏览器的 js 代码有很大概率是无法直接在 node 中跑起来的，原因就是 node 的全局对象中没有浏览器的对象，如 window，document，甚至连<code>localStorage</code> 都有！</p>
<p>这说明什么，往常如果你从别的网站扣了一段代码下来，想在 node 运行会发现什么 window is not defined，xxx is not defined。如果想在 node 运行，你必须需要补齐浏览器的环境，此外可以借助 js-dom，happy-dom 等 npm 包。而 window，xxx 这些全局只有浏览器才定义的全局对象在 deno 的运行时同样定义了，可以到<a href="https://deno.land/manual@v1.29.3/runtime/web_platform_apis#using-web-platform-apis" target="_blank" rel="noopener noreferrer" title="这里">这里</a>查看支持的 Web 平台 API。</p>
<p>虽说与真实浏览器全局对象有些许差异，但这也足够让开发者少做很多工作。比如 Web 逆向者通常要扣取浏览器的 js 代码，并补齐环境使其能够在 node 中运行，而有了 deno 这将变得非常轻松！</p>
<p><strong>与其说是 javascript/typescript 运行时，我更愿意说是浏览器运行时！</strong></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="web-框架">Web 框架<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#web-%E6%A1%86%E6%9E%B6" class="hash-link" aria-label="Web 框架的直接链接" title="Web 框架的直接链接">​</a></h3>
<p>你可以在 <a href="https://deno.land/manual@v1.29.2/getting_started/web_frameworks" target="_blank" rel="noopener noreferrer" title="Web Frameworks">Web Frameworks</a> 中看到 deno 官方所推荐的 Web 框架，其中 <a href="https://deno.land/manual@v1.29.2/getting_started/web_frameworks#fresh" target="_blank" rel="noopener noreferrer" title="Fresh">Fresh</a> 也是最为推荐使用的（后续我也会尝试使用该框架）。</p>
<p>而在 node 社区中，你会看到像 express，koa，nestjs 等等这种非 Node 官方或大背景的 web 框架（而且还很多），而这时对于初学者而言，就有点不知道该如何做出抉择。</p>
<p>而像 java 中你完全可以不用担心该学什么，说学 spring 就是在学 java 这可一点都不为过。可能这也是国内 java，尤其是 spring 的开发者尤为诸多的原因。</p>
<p>吐槽归吐槽，但我想表明的是在有官方的支持下，用户和开发者能够统一使用某个框架，一起维护与使用一个更好的框架。而不是个个 Web 框架的都有各自的优缺点，让使用者去选择，搞得这个框架是另一个框架的轮子一般。</p>
<p>所以我认为这种支持是很有必要。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="公共托管服务">公共托管服务<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#%E5%85%AC%E5%85%B1%E6%89%98%E7%AE%A1%E6%9C%8D%E5%8A%A1" class="hash-link" aria-label="公共托管服务的直接链接" title="公共托管服务的直接链接">​</a></h3>
<p><a href="https://dash.deno.com/" target="_blank" rel="noopener noreferrer" title="Project - Deploy (deno.com)">Project - Deploy (deno.com)</a></p>
<p>deno 像 vercel/netfily 一样提供了一个代码托管服务，可以将你的 deno 应用部署上去。对，目前来看还无法部署前端应用，因为要指明一个入门文件（main.ts）。</p>
<p>你可以通过 <a href="https://kuizuo.deno.dev/" target="_blank" rel="noopener noreferrer" title="https://kuizuo.deno.dev/">https://kuizuo.deno.dev/</a> 来访问我使用 deno Deploy 所创建的一个在线项目。将会输出一个<code>Hello World!</code> 的页面。</p>
<p>提供一个免费的线上环境体验，对开发者而言尤为重要，尤其是在将自己的项目成果分享给他人展示时，成就感油然而生。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="node-转-deno-开发的一些帮助">node 转 deno 开发的一些帮助<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#node-%E8%BD%AC-deno-%E5%BC%80%E5%8F%91%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B8%AE%E5%8A%A9" class="hash-link" aria-label="node 转 deno 开发的一些帮助的直接链接" title="node 转 deno 开发的一些帮助的直接链接">​</a></h2>
<p>deno 相关的亮点我也差不多介绍完了，也许你对 deno 已经有一丝兴趣想尝试一番，以下我整理的对你也许有所帮助。</p>
<ul>
<li>
<p>如果你是一个 Node 用户，考虑切换到 Deno，这里有一个<a href="https://link.juejin.cn/?target=https://deno.land/manual/node/cheatsheet" target="_blank" rel="noopener noreferrer" title="官方小抄">官方小抄</a>来帮助你。</p>
</li>
<li>
<p>如果你不想刷 deno 文档，想快速上手 deno 的话，这里我建议推荐看看 deno 官方所推荐的<a href="https://deno.land/manual@v1.29.4/examples" target="_blank" rel="noopener noreferrer" title="deno代码例子 ">deno 代码例子 </a>，能够非常快速有效了直接了解 deno 标准库以及依赖导入导出。</p>
</li>
<li>
<p>deno 是集成了 node 与 npm 的，也就是说允许直接使用 npm 包与 node 标准库，如果你想用 deno 来写 node，也行，详看<a href="https://deno.land/manual@v1.29.4/node#interoperating-with-nodejs-and-npm" target="_blank" rel="noopener noreferrer" title="Interoperating with Node.js and npm">Interoperating with Node.js and npm</a>。</p>
</li>
<li>
<p>想要在 deno 中连接数据库，可看<a href="https://deno.land/manual@v1.29.4/basics/connecting_to_databases#connecting-to-databases" target="_blank" rel="noopener noreferrer" title="Connecting to Databases">Connecting to Databases</a>。</p>
</li>
<li>
<p>如果想看 deno 如何使用 deno 生态的 Web 框架创建一个 Web 服务，推荐<a href="https://fresh.deno.dev/" target="_blank" rel="noopener noreferrer" title="fresh">fresh</a>框架，并查看该例子<a href="https://github.com/denoland/fresh/tree/main/examples/counter" target="_blank" rel="noopener noreferrer" title="fresh/examples/counter">fresh/examples/counter</a></p>
</li>
</ul>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="node-火吗">node 火吗?<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#node-%E7%81%AB%E5%90%97" class="hash-link" aria-label="node 火吗?的直接链接" title="node 火吗?的直接链接">​</a></h2>
<p>关于 deno 就暂且落下笔墨，不妨思考一个问题，node 火吗。</p>
<p>作为 node 开发者，我肯定会说 node 火，不过更多是对 javascript 来说火。</p>
<p>如今 typescript 大势所趋，说 javascript 就等同于说 typescript，而 javascript 和 node 绑定已成事实，而前端也与 javascript 所绑定，如今的前端工程师要是不会 node，都不好意思说自己是个前端工程师。就现阶段看，没了 nodejs，前端技术得倒退十年（不夸张）。</p>
<p>如果是在 Web 前端，Node 确实已经火的一塌糊涂了，然而它的诞生并不是为了 Web 前端，而是希望将 javascript 作为服务器端语言发展。只是后来没有想到的是 Node.js 在前端领域却大放异彩，造就了如今大前端的盛世。</p>
<p>所以在 Web 后端的领域，Node 确实是不温不火，更多的公司都宁可选主流的后端开发语言，而不是优先考虑 Node。不过倒是在 Serverless 领域中，Node 有着一席之地。</p>
<p>所以我想 deno 的出现，不仅是针对 Node.js 的缺陷，更是针对 Node.js 后端开发的不足。至于说 deno 能否完成原先 node 的使命，只有时间能给我们答案。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="总结">总结<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接">​</a></h2>
<p>从上述看来，你应该会发现 deno 并不和 node 一样是一个纯运行时环境。因为他不仅仅做了 javascript/typescript 运行时环境，还做了很多开发者好评的功能，一个为 javascript/typescript 提供更好的开发支持的产品。</p>
<p>但好评并不能直接决定销量，这些功能看似可有可无，没有激起用户从 Node.js 切换过来的杰出之处。就我体验完发现，好像 deno 能做的东西 node 大部分也能做，只是相对繁琐重复一些而已。<strong>但人们更倾向于做一件繁琐重复的事情，而不是做一个新的事情。</strong></p>
<p>扪心自问，我真的很希望 deno 能火，就开发体验而言，比 node 好用太多了，但好用的东西代表不了用的人就多，这个领域中，生态尤为重要。想要让 node 用户转到 deno 开发还有很长一段路要走。</p>
<p>再来反问自己，我现在会将 deno 作为 node 替代品吗，我想我和多数 node 开发者一样，都不会将 deno 作为主力语言(因为有很多项目都已经使用node来进行开发与推动)。但作为个人开发者，尤其是 node 开发者，我认为还是非常有必要去尝试一番 deno，亲手目睹"下一代Node"。</p>
<p>希望本文能对你了解 deno 有所帮助。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="相关推荐文章">相关推荐文章<a href="https://kuizuo.me/blog/deno-is-not-only-a-javascript-runtime#%E7%9B%B8%E5%85%B3%E6%8E%A8%E8%8D%90%E6%96%87%E7%AB%A0" class="hash-link" aria-label="相关推荐文章的直接链接" title="相关推荐文章的直接链接">​</a></h2>
<p><a href="https://juejin.cn/post/7168383367602241550" target="_blank" rel="noopener noreferrer" title="Deno vs. Node.js哪个更好 - 掘金 (juejin.cn)">Deno vs. Node.js 哪个更好 - 掘金 (juejin.cn)</a></p>
<p><a href="https://juejin.cn/post/6956461134299955213" target="_blank" rel="noopener noreferrer" title="为什么 Deno 没有众望所归？超越 Node.js 还要做些什么？ - 掘金 (juejin.cn)">为什么 Deno 没有众望所归？超越 Node.js 还要做些什么？ - 掘金 (juejin.cn)</a></p>
<p><a href="https://www.zhihu.com/question/327534747" target="_blank" rel="noopener noreferrer" title="连发明人都抛弃node.js了，还有前途吗？ - 知乎 (zhihu.com)">连发明人都抛弃 node.js 了，还有前途吗？ - 知乎 (zhihu.com)</a></p>
<p><a href="https://www.zhihu.com/question/517617266" target="_blank" rel="noopener noreferrer" title="已经 2022 年了 Deno 现在怎么样了? - 知乎 (zhihu.com)">已经 2022 年了 Deno 现在怎么样了? - 知乎 (zhihu.com)</a></p>]]></content>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="deno" term="deno"/>
        <category label="node" term="node"/>
        <category label="javascript" term="javascript"/>
        <category label="typescript" term="typescript"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Rust实现MD5加密并打包成WebAssembly调用]]></title>
        <id>https://kuizuo.me/blog/rust-wasm-md5</id>
        <link href="https://kuizuo.me/blog/rust-wasm-md5"/>
        <updated>2023-01-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[我初识 WebAssembly 是当初想要分析某个网站的加密算法，最终定位到了一个 .wasm 文件，没错，这个就是 WebAssembly 的构建产物，能够直接运行在浏览器中。在我当时看来这门技术很先进，不过如今看来绝大多数的 web 应用貌似都没使用上，迄今为止我也只在这个网站中看到使用 WebAssembly 的（也许有很多，只是没实质分析过）。]]></summary>
        <content type="html"><![CDATA[<p>我初识 WebAssembly 是当初想要分析某个网站的加密算法，最终定位到了一个 <code>.wasm</code> 文件，没错，这个就是 WebAssembly 的构建产物，能够直接运行在浏览器中。在我当时看来这门技术很先进，不过如今看来绝大多数的 web 应用貌似都没使用上，迄今为止我也只在这个网站中看到使用 WebAssembly 的（也许有很多，只是没实质分析过）。</p>
<p>恰好最近正在接触 Rust，而 Rust 开发 WebAssembly 也非常方便，因此本文算是我对 Rust + WebAssembly 的初探。</p>
<p>有关 <a href="https://developer.mozilla.org/zh-CN/docs/WebAssembly" target="_blank" rel="noopener noreferrer">WebAssembly </a>不做过多介绍，你可以到 <a href="https://developer.mozilla.org/zh-CN/docs/WebAssembly" target="_blank" rel="noopener noreferrer">MDN</a> 中查看相关介绍。本文重点于 Rust + WebAssembly 实践与相关工具，在 <a href="https://github.com/rustwasm" target="_blank" rel="noopener noreferrer">Rust and WebAssembly (github.com)</a> 或 <a href="https://github.com/rwasm" target="_blank" rel="noopener noreferrer">https://github.com/rwasm</a> 中查看 rustwasm 相关生态。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="使用-wasm-pack-打包-rust-为-wasm-文件">使用 <a href="https://rustwasm.github.io/wasm-pack/installer/" target="_blank" rel="noopener noreferrer">wasm-pack</a> 打包 rust 为 wasm 文件<a href="https://kuizuo.me/blog/rust-wasm-md5#%E4%BD%BF%E7%94%A8-wasm-pack-%E6%89%93%E5%8C%85-rust-%E4%B8%BA-wasm-%E6%96%87%E4%BB%B6" class="hash-link" aria-label="使用-wasm-pack-打包-rust-为-wasm-文件的直接链接" title="使用-wasm-pack-打包-rust-为-wasm-文件的直接链接">​</a></h2>
<p>下载 wasm-pack，用于将 rust 代码打包成 .wasm 文件</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%)">cargo</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">install</span><span class="token plain"> wasm-pack</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>使用 cargo 有可能无法安装 wasm-pack（笔者就安装不了 openssl-sys），可以到 <a href="https://rustwasm.github.io/wasm-pack/installer/" target="_blank" rel="noopener noreferrer">wasm-pack</a> 官网下载对应的二进制文件进行安装。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="构建-rust-lib">构建 rust lib<a href="https://kuizuo.me/blog/rust-wasm-md5#%E6%9E%84%E5%BB%BA-rust-lib" class="hash-link" aria-label="构建 rust lib的直接链接" title="构建 rust lib的直接链接">​</a></h3>
<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%)">cargo</span><span class="token plain"> new </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">--lib</span><span class="token plain"> hello-wasm</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>将会创建 rust 库工程，并创建 <code>src/lib.rs</code>。修改为以下内容（先不必在意代码）</p>
<div class="language-rust 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>src/lib.rs<span style="flex:1;text-align:right">rust</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-rust 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%)">extern</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">crate</span><span class="token plain"> </span><span class="token module-declaration namespace">wasm_bindgen</span><span class="token punctuation" style="color:hsl(119, 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%)">use</span><span class="token plain"> </span><span class="token namespace">wasm_bindgen</span><span class="token namespace punctuation" style="color:hsl(119, 34%, 47%)">::</span><span class="token namespace">prelude</span><span class="token namespace punctuation" style="color:hsl(119, 34%, 47%)">::</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><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 attribute attr-name" style="color:hsl(35, 99%, 36%)">#[wasm_bindgen]</span><span 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%)">extern</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%)">pub</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:hsl(221, 87%, 60%)">alert</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">s</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%)">&amp;</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">str</span><span class="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 attribute attr-name" style="color:hsl(35, 99%, 36%)">#[wasm_bindgen]</span><span 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%)">pub</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:hsl(221, 87%, 60%)">greet</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><span class="token operator" style="color:hsl(221, 87%, 60%)">&amp;</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">str</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 function" style="color:hsl(221, 87%, 60%)">alert</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token operator" style="color:hsl(221, 87%, 60%)">&amp;</span><span class="token macro property" style="color:hsl(5, 74%, 59%)">format!</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"> 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 punctuation" style="color:hsl(119, 34%, 47%)">;</span><span 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></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>接着在 Cargo.toml 文件中添加 wasm-bindgen 依赖，<code>wasm-bindgen</code> 来提供 JavaScript 和 Rust 类型之间的桥梁，允许 JavaScript 使用字符串调用 Rust API，或调用 Rust 函数来捕获 JavaScript 异常。</p>
<div class="language-toml 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>Cargo.toml<span style="flex:1;text-align:right">toml</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-toml 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 table class-name" style="color:hsl(35, 99%, 36%)">package</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token key property" style="color:hsl(5, 74%, 59%)">name</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 string" style="color:hsl(119, 34%, 47%)">"hello-wasm"</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token key property" style="color:hsl(5, 74%, 59%)">version</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 string" style="color:hsl(119, 34%, 47%)">"0.1.0"</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 punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token table class-name" style="color:hsl(35, 99%, 36%)">lib</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token key property" style="color:hsl(5, 74%, 59%)">crate-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 punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token string" style="color:hsl(119, 34%, 47%)">"cdylib"</span><span class="token punctuation" style="color:hsl(119, 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 punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token table class-name" style="color:hsl(35, 99%, 36%)">dependencies</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain"></span><span class="token key property" style="color:hsl(5, 74%, 59%)">wasm-bindgen</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 string" style="color:hsl(119, 34%, 47%)">"0.2"</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></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_vMh4" id="打包">打包<a href="https://kuizuo.me/blog/rust-wasm-md5#%E6%89%93%E5%8C%85" class="hash-link" aria-label="打包的直接链接" title="打包的直接链接">​</a></h3>
<div class="language-rust 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-rust 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">wasm</span><span class="token operator" style="color:hsl(221, 87%, 60%)">-</span><span class="token plain">pack build</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>WebAssembly 构建产物将会输出在 pkg 目录下，如下</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">├─pkg</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 punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">gitignore</span><span 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">  ├─hello_wasm</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">d</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">ts</span><span 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">  ├─hello_wasm</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 operator" style="color:hsl(221, 87%, 60%)">|</span><span class="token plain">  ├─hello_wasm_bg</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 operator" style="color:hsl(221, 87%, 60%)">|</span><span class="token plain">  ├─hello_wasm_bg</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">wasm</span><span 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">  └─hello_wasm_bg</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">wasm</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">d</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token property-access">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>
<div class="theme-admonition theme-admonition-info admonition_lhEC alert alert--info"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p>如果想当 npm 包发布的话，可以添加 —scope 参数，将会在 pkg 下生成 package.json 文件用于发布或当做一个 npm 包来使用，这样也可以在前端工程中直接当做一个模块来导入使用。</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">wasm-pack build </span><span class="token parameter variable" style="color:hsl(221, 87%, 60%)">--scope</span><span class="token plain"> mynpmusername</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>
<p>借助 <a href="https://rustwasm.github.io/wasm-pack/installer/" target="_blank" rel="noopener noreferrer">wasm-pack</a> 可以非常轻松的将 rust 打包成 wasm，同时还提供了 js 相关支持。直接打包成 js 可导入的 npm 包，而不是让用户导入 wasm 文件然后通过浏览器 <code>WebAssembly</code> 对象来加载 WebAssembly 代码，其他语言的 WebAssembly 开发也是如此。</p>
<p>此外 <a href="https://rustwasm.github.io/" target="_blank" rel="noopener noreferrer">rustwasm</a> 还提供了对应的模板 <a href="https://github.com/rustwasm/wasm-pack-template" target="_blank" rel="noopener noreferrer">rustwasm/wasm-pack-template</a>，可以帮你省去上面的一系列配置操作，专注于你的 wasm 开发。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="运行">运行<a href="https://kuizuo.me/blog/rust-wasm-md5#%E8%BF%90%E8%A1%8C" class="hash-link" aria-label="运行的直接链接" title="运行的直接链接">​</a></h3>
<p>由于上面我们已经将其打包成了一个 npm 包，只需要将配置好 package.json 的依赖即可，本地的话可通过下方格式，将 pkg 目录更改为 hello-wasm，并放置在根目录下。</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="codeBlockTitle_P25_"><span></span>package.json<span style="flex:1;text-align:right">json</span></div><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 plain">  </span><span class="token property" style="color:hsl(5, 74%, 59%)">"dependencies"</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 property" style="color:hsl(5, 74%, 59%)">"hello-wasm"</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%)">"file:./hello-wasm"</span><span 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>这时候就可以通过 js 直接导入使用</p>
<div class="language-rust 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-rust 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"> js </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%)">import</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">"./hello-wasm/hello_wasm.js"</span><span class="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">js</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">then</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">js </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">  js</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">greet</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">"WebAssembly"</span><span class="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><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>在 vite 生态中有个 <a href="https://github.com/rwasm/vite-plugin-rsw" target="_blank" rel="noopener noreferrer">rwasm/vite-plugin-rsw</a> 插件，能够在 vite 中快速使用 wasm-pack。下文中的一个应用示例也将采用该插件进行开发。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="rust-实现-md5-算法">Rust 实现 MD5 算法<a href="https://kuizuo.me/blog/rust-wasm-md5#rust-%E5%AE%9E%E7%8E%B0-md5-%E7%AE%97%E6%B3%95" class="hash-link" aria-label="Rust 实现 MD5 算法的直接链接" title="Rust 实现 MD5 算法的直接链接">​</a></h2>
<p>回到一开始的标题，在实现这个功能我一般会想 js 如何实现 MD5 算法，通常来说 MD5 算法是个比较流行的加密算法，通过搜索引擎能够快速帮我找到一份 js 的 MD5 算法。不过我更习惯通过包管理器导入的加密库，如<a href="https://www.npmjs.com/package/crypto-js" target="_blank" rel="noopener noreferrer">crypto-js</a>。</p>
<p>同理，在 rust 中可以到 <a href="https://crates.io/" target="_blank" rel="noopener noreferrer">crates.io</a> 中也可以找到你想要的库，如 <a href="https://crates.io/crates/digest" target="_blank" rel="noopener noreferrer">digest</a>，不过我这里主要是实现 MD5 算法便使用的是 <a href="https://crates.io/crates/md-5" target="_blank" rel="noopener noreferrer">md-5</a>。以下是我的封装代码。</p>
<div class="language-rust 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-rust 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%)">use</span><span class="token plain"> </span><span class="token namespace">md5</span><span class="token namespace punctuation" style="color:hsl(119, 34%, 47%)">::</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Digest</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Md5</span><span class="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%)">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:hsl(221, 87%, 60%)">md5</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"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&amp;</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">str</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%)">-&gt;</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">String</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"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">mut</span><span class="token plain"> hasher </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Md5</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">::</span><span class="token function" style="color:hsl(221, 87%, 60%)">new</span><span 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">    hasher</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">input</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">as_bytes</span><span 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" 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%)">let</span><span class="token plain"> result </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> hasher</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">finalize</span><span 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 macro property" style="color:hsl(5, 74%, 59%)">format!</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">"{:x}"</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><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%)">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:hsl(221, 87%, 60%)">main</span><span class="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%)">let</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 function" style="color:hsl(221, 87%, 60%)">md5</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">"123456"</span><span class="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 macro property" style="color:hsl(5, 74%, 59%)">println!</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"> result</span><span class="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></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-rust 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>lib.rs<span style="flex:1;text-align:right">rust</span></div><div class="codeBlockContent_m3Ux"><pre class="prism-code language-rust 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%)">extern</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">crate</span><span class="token plain"> </span><span class="token module-declaration namespace">wasm_bindgen</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">;</span><span 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%)">extern</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">crate</span><span class="token plain"> </span><span class="token module-declaration namespace">md5</span><span class="token punctuation" style="color:hsl(119, 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%)">use</span><span class="token plain"> </span><span class="token namespace">wasm_bindgen</span><span class="token namespace punctuation" style="color:hsl(119, 34%, 47%)">::</span><span class="token namespace">prelude</span><span class="token namespace punctuation" style="color:hsl(119, 34%, 47%)">::</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><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%)">use</span><span class="token plain"> </span><span class="token namespace">md5</span><span class="token namespace punctuation" style="color:hsl(119, 34%, 47%)">::</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Digest</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Md5</span><span class="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 attribute attr-name" style="color:hsl(35, 99%, 36%)">#[wasm_bindgen]</span><span 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%)">pub</span><span class="token plain"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:hsl(221, 87%, 60%)">md5</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"> </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&amp;</span><span class="token keyword" style="color:hsl(301, 63%, 40%)">str</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">-&gt;</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">String</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"> </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">mut</span><span class="token plain"> hasher </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> </span><span class="token class-name" style="color:hsl(35, 99%, 36%)">Md5</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">::</span><span class="token function" style="color:hsl(221, 87%, 60%)">new</span><span 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">    hasher</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">input</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">as_bytes</span><span 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" 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%)">let</span><span class="token plain"> result </span><span class="token operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token plain"> hasher</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">finalize</span><span 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 macro property" style="color:hsl(5, 74%, 59%)">format!</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">"{:x}"</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><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></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>此时通过 wasm-pack 将上述代码打包成 npm 包形式即可在 js 中调用 rust 提供的 md5 函数，至此就已经完成了本标题的内容了。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="在项目中使用">在项目中使用<a href="https://kuizuo.me/blog/rust-wasm-md5#%E5%9C%A8%E9%A1%B9%E7%9B%AE%E4%B8%AD%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="在项目中使用的直接链接" title="在项目中使用的直接链接">​</a></h2>
<p>这里我所借用 <a href="https://github.com/rwasm/vite-plugin-rsw" target="_blank" rel="noopener noreferrer">rwasm/vite-plugin-rsw</a> 插件，在 vite 中配合 wasm-pack 进行开发的一个实例。代码部分就不做解读了，有兴趣可自行到翻阅源码：<a href="https://github.com/kuizuo/rust-wasm-md5" target="_blank" rel="noopener noreferrer">kuizuo/rust-wasm-md5</a></p>
<p>在线地址：<a href="http://rust-wasm-md5.kuizuo.me/" target="_blank" rel="noopener noreferrer">http://rust-wasm-md5.kuizuo.me</a> （不保证地址长期可用）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/_XHPNCbC-B.png" alt="" class="img_c29J"></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="思考为何不使用-js-的-md5-而是-wasm-的-md5">思考：为何不使用 js 的 md5 而是 wasm 的 md5<a href="https://kuizuo.me/blog/rust-wasm-md5#%E6%80%9D%E8%80%83%E4%B8%BA%E4%BD%95%E4%B8%8D%E4%BD%BF%E7%94%A8-js-%E7%9A%84-md5-%E8%80%8C%E6%98%AF-wasm-%E7%9A%84-md5" class="hash-link" aria-label="思考：为何不使用 js 的 md5 而是 wasm 的 md5的直接链接" title="思考：为何不使用 js 的 md5 而是 wasm 的 md5的直接链接">​</a></h2>
<p>众所周知，你在浏览器中按下 F12 打开 DevTools，并选择源代码面板中就可以看到当前访问的网站的所有代码。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/6019y_U19n.png" alt="" class="img_c29J"></p>
<p>而对于一些具有熟练度的逆向分析者中，如果不经过任何处理的代码被打包到生产环境中能够快速的定位出某个功能的具体代码位置。</p>
<p>而通过 wasm 就能很有效的将代码隐藏起来，不让逆向分析者查看，就像下面这样</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/BbA3n6wFws.png" alt="" class="img_c29J"></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/81tgfDE_P7.png" alt="" class="img_c29J"></p>
<p>这里我并没有将 md5 更改成不易猜测的名字，你也可自行下断点尝试一番，定位代码。当你定位到具体代码后，就会得到上图的二进制代码格式，几乎无法解读其意思。</p>
<p>不过虽说解读不出 wasm 的原代码（至少目前来说很难反编译成原始代码），但可以通过扣代码的方式来调用 wasm 对外提供的函数（这里为 md5 函数）。</p>
<p>这里仅是 wasm 的一种实际用例，更多情况下应该还是用 Wasm 来提高 web 应用性能的。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="相关链接">相关链接<a href="https://kuizuo.me/blog/rust-wasm-md5#%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5" class="hash-link" aria-label="相关链接的直接链接" title="相关链接的直接链接">​</a></h2>
<p><a href="https://developer.mozilla.org/zh-CN/docs/WebAssembly/Rust_to_wasm" target="_blank" rel="noopener noreferrer">编译 Rust 为 WebAssembly - WebAssembly | MDN (mozilla.org)</a></p>
<p><a href="https://rustwasm.github.io/" target="_blank" rel="noopener noreferrer">Rust and WebAssembly</a></p>
<p><a href="https://rustmagazine.github.io/rust_magazine_2021/chapter_2/rust_wasm_frontend.html" target="_blank" rel="noopener noreferrer">前端入门 ｜ Rust 和 WebAssembly - Rust 精选</a></p>
<p><a href="https://github.com/rwasm/vite-plugin-rsw" target="_blank" rel="noopener noreferrer">rwasm/vite-plugin-rsw: 🦞 wasm-pack plugin for Vite (github.com)</a></p>]]></content>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="rust" term="rust"/>
        <category label="wasm" term="wasm"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[2022 · 逆向到Web开发]]></title>
        <id>https://kuizuo.me/blog/2022-year-end-summary</id>
        <link href="https://kuizuo.me/blog/2022-year-end-summary"/>
        <updated>2022-12-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[距离上一篇博文有一个月之久，距离上次编写代码也有一周之久，由于疫情封控全面放开，加上福建省教育厅通知的提前返乡，反而让我感到有些不适。往常这个时间点我忙于期末考试，会把代码的事情放一边，等到彻底放假后，开始闭关潜心学习。然而剩余的半个月变成了线上形式，课还是要上，考试还是要考，虽说身在家乡，但心不在焉的。]]></summary>
        <content type="html"><![CDATA[<p>距离上一篇博文有一个月之久，距离上次编写代码也有一周之久，由于疫情封控全面放开，加上福建省教育厅通知的提前返乡，反而让我感到有些不适。往常这个时间点我忙于期末考试，会把代码的事情放一边，等到彻底放假后，开始闭关潜心学习。然而剩余的半个月变成了线上形式，课还是要上，考试还是要考，虽说身在家乡，但心不在焉的。</p>
<p>12 月已过半，也是时候该写年终总结了。迄今为止，我已学习了 3 年半的编程（还好不是两年半）。当下的技术不再是当初只会易语言的小伙了。只是当下已没有当初如此强烈的热情与精力了，我称之为老了。</p>
<p>今年主要总结我为何从逆向转 Web 开发，并明确我未来所要走的方向，也是本文的主题。<strong>仅作为个人感悟，不作为建议参考。</strong></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="为什么要写年终总结">为什么要写年终总结？<a href="https://kuizuo.me/blog/2022-year-end-summary#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%86%99%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93" class="hash-link" aria-label="为什么要写年终总结？的直接链接" title="为什么要写年终总结？的直接链接">​</a></h2>
<p>我从高中开始，每年都会记录这一年所发生的比较有印象的事情并写下自己的感悟。所以写年终算是个人习惯，并且我希望能够一直坚持下去，写到不能写为止。</p>
<p>写年终总结是个非常好的自我总结与反思的方式，总结这一年自身的变化，有哪些精彩与满足，有哪些遗憾和不足。同时定制明年的规划，以该目标不断前行，而不是漫无目的地活着，就失去了很多人生的意义。</p>
<p>同时也算是自我评价与建议，很多时候我们会收到很多别人有关自己的评价与建议，可人总会对他人有莫名的排斥感，就难以虚心听从他人的建议，从而犯错许多（说的是我就对了）。年终总结还有个作用就是弥补自己对某件事情未来可能要发生的情况，要如何做到不犯错，预先有个明确的预防意识。</p>
<p>即便可能要花数天的周期去回顾与总结，我也很愿意去总结，我常说回顾过去，就是在仰望未来。写年终所做的也就是这个过程。</p>
<p>写年终总结也算是种分享，分享自己的开发经历，当他人阅读时或许有所启发。</p>
<p><strong>过去的，就过去了，别将当下的遗憾留到未来，这就是年终的最大意义。</strong></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="为什么是-web-开发而不是逆向">为什么是 Web 开发？而不是逆向<a href="https://kuizuo.me/blog/2022-year-end-summary#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%98%AF-web-%E5%BC%80%E5%8F%91%E8%80%8C%E4%B8%8D%E6%98%AF%E9%80%86%E5%90%91" class="hash-link" aria-label="为什么是 Web 开发？而不是逆向的直接链接" title="为什么是 Web 开发？而不是逆向的直接链接">​</a></h2>
<p>今年大部分的开发时间都花在 Web 开发上，在此期间也接触到许多技术，并通过博客笔记的方式记录下来。</p>
<p>我很庆幸我的 Web 开发从一个 Beginner 到 Intermediate，现在回忆整个学习路程，一路学得都很野很随意，从未系统学习过，总是学到一半，就自认为已经掌握了，便开始进行实战项目，可以说很多知识都是在实战探索中了解的。</p>
<p>虽然很多人都直称我为大佬，但我离 Advanced 还有一大段的路途要走，而这段路途是无比的艰难与漫长。不过好在路不歪，只要肯走终会到达终点。</p>
<p>而带我入门的逆向技术，在今年没有丝毫的长进，说得过分点，就是弃坑了。也正如标题所说，至于缘由，细看下文。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="逆向">逆向<a href="https://kuizuo.me/blog/2022-year-end-summary#%E9%80%86%E5%90%91" class="hash-link" aria-label="逆向的直接链接" title="逆向的直接链接">​</a></h3>
<p>熟悉我的人应该知道我之前是做爬虫与逆向分析，但是为何今年的技术栈彻底转型到 Web 开发上。</p>
<p>我常常和别人说起我的技术栈转型（从逆向转到开发），不过比较多的都会比较好奇我为什么不继续深造下去。</p>
<p>在此我也回顾了我<strong>从逆向转变到 Web 开发</strong>的过程，顺带也回答这个问题。不过在这里先说说我个人对逆向的看法：</p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="逆向需要的技术知识面比较广">逆向需要的技术知识面比较广。<a href="https://kuizuo.me/blog/2022-year-end-summary#%E9%80%86%E5%90%91%E9%9C%80%E8%A6%81%E7%9A%84%E6%8A%80%E6%9C%AF%E7%9F%A5%E8%AF%86%E9%9D%A2%E6%AF%94%E8%BE%83%E5%B9%BF" class="hash-link" aria-label="逆向需要的技术知识面比较广。的直接链接" title="逆向需要的技术知识面比较广。的直接链接">​</a></h4>
<p>就我接触逆向的过程来说，接触了易语言，JavaScript，Python，Java，Php 等等编程语言。</p>
<p>使用过了一堆的逆向工具，如 Frida、IDA、JEB、jadx 等等（我目前能想到比较有名的）。此外还有一堆知识，包括但不限于以下技术：自动化脚本、TCP/HTTP 协议、抓包、爬虫、加密学、图像识别（验证码、滑块位置）、汇编、反编译、AST 反混淆。</p>
<p>**要我说我在逆向中学到的不是如何使用这些工具或掌握某个技能，而是锻炼出一定的阅读与分析代码能力。**工具与技能总是瞬息万变，但阅读与分析代码能力却是实实在在，一成不变的，也是逆向中最值得学习的。</p>
<p>有接触过 <a href="https://baike.baidu.com/item/ctf/9548546" target="_blank" rel="noopener noreferrer">CTF</a> 想必再熟悉不过逆向工程的技术面广了。</p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="越来越多的网站或应用程序不断加强安全防护未来只会越来越难逆向">越来越多的网站或应用程序不断加强安全防护，未来只会越来越难逆向。<a href="https://kuizuo.me/blog/2022-year-end-summary#%E8%B6%8A%E6%9D%A5%E8%B6%8A%E5%A4%9A%E7%9A%84%E7%BD%91%E7%AB%99%E6%88%96%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E4%B8%8D%E6%96%AD%E5%8A%A0%E5%BC%BA%E5%AE%89%E5%85%A8%E9%98%B2%E6%8A%A4%E6%9C%AA%E6%9D%A5%E5%8F%AA%E4%BC%9A%E8%B6%8A%E6%9D%A5%E8%B6%8A%E9%9A%BE%E9%80%86%E5%90%91" class="hash-link" aria-label="越来越多的网站或应用程序不断加强安全防护，未来只会越来越难逆向。的直接链接" title="越来越多的网站或应用程序不断加强安全防护，未来只会越来越难逆向。的直接链接">​</a></h4>
<p>逆向分析，说白话就是去看别人的代码，进行一些修改手段，达到自己想要的目的。比如修改某个软件的标题或作者信息、将别人的代码“偷”过来用、爬取某些网站或软件的数据。</p>
<p>但是随着现在越来越多潜在的安全问题，很多框架底层，服务厂商，都会对一些可能有安全问题的代码进行警告或者底层处理。比如使用 ORM 框架能够有效防止 SQL 注入，前端框架中涉及 XSS 攻击也会有相应的错误提示与处理，再如浏览器跨域以及跨站点 cookie 不共享，都是为了用户的安全而去考虑的。这样的例子有太多了。</p>
<p>简单说说安卓逆向的过程：拿到一个 Apk，发现有加壳（给代码加固让逆向者不易于看到源代码），这时候就需要通过脱壳才能查看到源代码；此时就算脱完了壳，接下来可能将面对经过混淆过的代码，这份代码难以用人眼去阅读，不调试运行，单靠静态分析很难分析出东西，这时候可能就要借助 AST 对代码进行还原；还原完了配合动静分析将代码给扣出来，而在分析的时候可能又有各种检测，比如抓包，反调试以阻碍逆向进度；最后就算逆向工作都做完了，代码也扣下来了，还要尝试运行扣完的代码，这时候极大可能还不一定能运行，然后又要回头看看到底那一步做错了。</p>
<p>可以说逆向的工作就是不断地调试，不断分析，最终拿到想要的结果。整个过程可以说非常耗时且折磨人，心智如果不够强大，真的容易劝退（我当下耐心也早不如当初了）。但是得到最终的目的，将非常爽，成就感爆棚，相当于一个解了几小时的题，最终被攻克的感觉。</p>
<p>前阵子在逆向圈中，看到过一张图，大致也把我的逆向学习流程也表达了出来，最终我的逆向学习也确实止步在 Flutter 上。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/1cd67d5812e3061e_zy9LIkXWIM.jpg" alt="" class="img_c29J"></p>
<p>上面所说到的逆向技术中，例如加壳，代码混淆，反调试，风控等等，都是阻碍逆向手段。同样对于爬虫而已，通常会采取反爬措施，包括但不限于封 IP、封账号、JS 参数加密、代码混淆、浏览器指纹、TLS 指纹、验证等。而且防护手段可以说是越来越多，逆向的难度也就越来越大。并且在服务开发那边只需要修改一点东西，逆向可能就需要从头再来一遍。</p>
<p><strong>未来逆向的难度只增不减，但薪资可不一定保证这趋势。</strong></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="风险">风险<a href="https://kuizuo.me/blog/2022-year-end-summary#%E9%A3%8E%E9%99%A9" class="hash-link" aria-label="风险的直接链接" title="风险的直接链接">​</a></h4>
<p>从上面也不难看出逆向常常与安全挂钩，如果一个网站或软件不是那么好被逆向，那么间接说明安全性是比较高的。可一旦涉及到安全，同时又是互联网，就免不了网络安全与法律相关的风险。</p>
<p>在大多数逆向的对象（网站，软件）中，多数都是他人的劳动产物，当你未经他人允许的情况下，去爬取他人的数据，或是修改一些版权信息再次发布出来，就属于非法行为。本质和未经他人允许，偷人家的东西性质是一个样的。并且在有防护措施的情况下，绕过网站防护措施获取数据属于违背权利人意愿读取、收集数据，将有较大可能被认定为对计算机信息系统的 “侵入”。</p>
<p>要知道在逆向的行业中，有很多都是擦边灰产，至少我所在的逆向圈是这样的，抱着【仅供学习为参考，请勿用于非法用途】的想法做逆向工程。路子极易走歪，很少能够正常去走安全岗位的。相信你应该能看到许多类似的案件，如【某某程序员因非法侵入 xx 网站，获刑 x 年】。这不是危言耸听，目前国家对网络安全以及非法数据获取的打击力度，也将决定了这些案件将会越来越多，爬虫逆向也将会越来越邢。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="自身因素">自身因素<a href="https://kuizuo.me/blog/2022-year-end-summary#%E8%87%AA%E8%BA%AB%E5%9B%A0%E7%B4%A0" class="hash-link" aria-label="自身因素的直接链接" title="自身因素的直接链接">​</a></h3>
<p>简单介绍完我认为爬虫和逆向的看法后，再来说说自身因素。</p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="更想写代码而不是看代码">更想写代码，而不是看代码<a href="https://kuizuo.me/blog/2022-year-end-summary#%E6%9B%B4%E6%83%B3%E5%86%99%E4%BB%A3%E7%A0%81%E8%80%8C%E4%B8%8D%E6%98%AF%E7%9C%8B%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="更想写代码，而不是看代码的直接链接" title="更想写代码，而不是看代码的直接链接">​</a></h4>
<p>我是很享受写代码的过程，一份高质量的代码会让人赏心悦目。我为此特意学习下设计模式、重构技巧以及 TDD 测试。这些在逆向中基本难以涉及，但是这些对开发的体验和代码的健壮性都是非常重要的，也是让我个人觉得技术有所提升的技能点。</p>
<p>在逆向分析中，大部分时间都不是在写代码而是在看（分析）代码，而反编译出来的代码，很有可能是带有混淆过的代码，而你想要分析这一块代码的作用，只能去一步步调试，将代码啃下来，从而推断运行时某变量的值，或是某个函数的作用。</p>
<p>虽然说开发岗位中，有很多情况下也是在看代码的日子中度过的，但相比反编译阅读代码而言，至少不至于那么晦涩难懂。</p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="更想碰新技术而不是旧技术">更想碰新技术，而不是旧技术<a href="https://kuizuo.me/blog/2022-year-end-summary#%E6%9B%B4%E6%83%B3%E7%A2%B0%E6%96%B0%E6%8A%80%E6%9C%AF%E8%80%8C%E4%B8%8D%E6%98%AF%E6%97%A7%E6%8A%80%E6%9C%AF" class="hash-link" aria-label="更想碰新技术，而不是旧技术的直接链接" title="更想碰新技术，而不是旧技术的直接链接">​</a></h4>
<p>其次，在逆向分析中，遇到的网站或软件所用的技术一般都是较为稳定成熟的技术，而这些技术往往不是很新，因此需要去了解很多旧的技术，但这些旧技术仅仅只对逆向分析有用，甚至过段时间很有可能就会遗忘。并且要了解的旧技术还不少，学得将会特别杂。</p>
<p>而我又是一个喜新厌旧的人，对任何新鲜事物都抱有好奇尝试的态度。我终将认为旧的技术被淘汰是迟早的问题，新技术的出现肯定不是无缘无故的出现，必然是为了解决某些问题而诞生的，如性能，开发体验，安全等等。</p>
<p>这里有篇我对<a href="https://kuizuo.me/blog/talk-new-technologies-opinion">新技术的看法</a>，推荐阅读一番。</p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="更想开源而不是闭源">更想开源，而不是闭源<a href="https://kuizuo.me/blog/2022-year-end-summary#%E6%9B%B4%E6%83%B3%E5%BC%80%E6%BA%90%E8%80%8C%E4%B8%8D%E6%98%AF%E9%97%AD%E6%BA%90" class="hash-link" aria-label="更想开源，而不是闭源的直接链接" title="更想开源，而不是闭源的直接链接">​</a></h4>
<p>在逆向开发，不，是在灰产开发中，有很多代码是别指望开源出来的，这背后会涉及到商业利益或是版权等问题。一旦代码放出来，将意味着你的代码将有很大的可能被别人利用做坏事，最终甚至祸及到自己。所以大多数情况下，你多半只能将自己的应用发布出去，而不是将源码开源，通常也就带有商业化的性质。</p>
<p>我想任何开发者肯定是希望自己的代码走的更远，走的正规，而不是被拿来做违背自身意愿的事情。</p>
<p>开源不仅能为自己提升一些技术知名度，展现自身技术的一面，同时代码被他人使用与认可，这番成就感就足够继续坚持开源下去。因为我有很多技术都是通过开源项目中学到的，所以我能感受到开源带来的魅力，也是对于前人的崇拜，想走开源的原因。</p>
<p><strong>因为淋过雨，所以很想为别人撑把伞</strong></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="小结">小结<a href="https://kuizuo.me/blog/2022-year-end-summary#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接">​</a></h3>
<p>其实在今年出头我还特意复习了一波安卓逆向，为了更深入了解了更底层的知识与工具（当然现在忘得也差不多了），因一些特殊原因我的电子设备不在了，别问，问就是坏了到现在还没修好。就导致我的编程语言环境，逆向工具，虚拟机配置，代码等数据直接灰飞烟灭，也让我停滞了 1 个月的学习。</p>
<p>我想这才是我从逆向转 Web 开发的最重要的理由了。</p>
<p>逆向的学习对我开发有很大的帮助。例如开发中的安全问题，我在开发中都会时刻考虑去考虑该问题。如加壳、SQL 注入、代码混淆、接口限流、接口幂等性（Fiddle R 包）等等安全性问题。</p>
<p>我庆幸我学过逆向，让我学到很多在开发中比较难学到的技能，如调试，阅读源码，而这些也是绝大多数 Web 开发者都欠缺的能力。</p>
<p>逆向应该就暂以告终，未来会有很长的一段时间，甚至以后不再接触深入专研逆向。</p>
<p>每当回想，【当时那么难的加密算法都能搞得定，开发一个功能还能有多难】，保持着这种心态，也让我保持着开发。</p>
<p>也是逆向激起我对编程的兴趣，可以说没有接触逆向开发，我也不太可能会接触到 Web 开发。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="但-web-开发就一定好吗">但 Web 开发就一定好吗？<a href="https://kuizuo.me/blog/2022-year-end-summary#%E4%BD%86-web-%E5%BC%80%E5%8F%91%E5%B0%B1%E4%B8%80%E5%AE%9A%E5%A5%BD%E5%90%97" class="hash-link" aria-label="但 Web 开发就一定好吗？的直接链接" title="但 Web 开发就一定好吗？的直接链接">​</a></h2>
<p>上面对逆向的看法仅个人分析而言，但是 Web 开发就一定好吗？我不敢下绝对的肯定，不同人不一定适合相同的技术，<strong>但是我特别看好 Web 技术（尤其是 JavaScript，偏前端向）</strong>，以下是我看好的点。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="应用性广">应用性广<a href="https://kuizuo.me/blog/2022-year-end-summary#%E5%BA%94%E7%94%A8%E6%80%A7%E5%B9%BF" class="hash-link" aria-label="应用性广的直接链接" title="应用性广的直接链接">​</a></h3>
<p>目前仍有很多人还停留在 JavaScript 只能编写前端页面的水平，自从 Node.js 问世，目前绝大多数的应用都可以使用 JavaScript 进行编写。</p>
<p>换句话说，只要你会 JavaScript 就可以编写很多应用了。有个在国外经常被应用的一条和 JavaScript 有关系的著名定律：</p>
<p><strong>Atwood 定律：任何可以用 JavaScript 编写的应用程序，最终都会用 JavaScript 编写</strong></p>
<p>Web 前端开发不用多说，目前还得用 JavaScript 来编写。而后端开发通过 Node.js 也有一己之力。近几年特别流行跨平台开发，也就是一份代码，多端运行。</p>
<p>使用 React Native 或是 Uniapp 这样的前端应用框架，顺带去了解一下相应平台的 API，就能够编写出安卓或 IOS 应用，做过小程序开发肯定知道小程序的技术栈就使用到前端开发的技术栈，如果你会前端开发，就能很轻松的上手小程序开发了。</p>
<p>并且越来越多的软件都是网页版优先，然后再通过 Electron 这样的跨平台解决方案，实现不同平台间的程序。诸如 Vscode，Typeorm 等等便是其中的佼佼者。</p>
<p>不过本质上都是在不同应用的平台上套浏览器的壳，然后接入一些该平台的接口，因此普遍应用的体积与内存都相对比较大，与原生相比自然是略逊与原生，不过这点性能上的差异在如今硬件升级如此普遍的物质生活也显得微不足道了。</p>
<p>但借助浏览器自带的跨平台性，你所编写的应用能够非常轻松的让他人访问，只需要用户有个浏览器，并且通过上述跨平台的解决方案，也能够有效的将你的 web 应用转成其他平台的应用，而不是在耗费时间与精力去编写另一套代码。</p>
<p>在一些技术文档上，会有 API/SDK 的支持，而绝大多数都支持使用 JavaScript/Node.js，也许该功能本身并不是使用 JavaScript 来编写的，但完全可以通过 JavaScript 来轻松调用该功能。并且如今 JavaScript 生态如此庞大，你不必担心库/包的问题，npm 作为世界上最大的开放源代码的生态系统（包管理器），在这里你几乎找得到你所想要的库/包。</p>
<p>此外有越来越多的第三方服务平台（通常称 Fass，函数即服务），专门提供数据接口与用户鉴权，如 firebase/supabase 或是国产的云开发产品（如小程序）。前端程序员只需要专注与业务代码，借助第三方开发平台，就能够编写一个高可用的应用。反而也映衬了，Web 开发好像可以没有后端，但必须要有前端的观念。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="工作量将越来越多">工作量将越来越多<a href="https://kuizuo.me/blog/2022-year-end-summary#%E5%B7%A5%E4%BD%9C%E9%87%8F%E5%B0%86%E8%B6%8A%E6%9D%A5%E8%B6%8A%E5%A4%9A" class="hash-link" aria-label="工作量将越来越多的直接链接" title="工作量将越来越多的直接链接">​</a></h3>
<p>这句话的意思可能有点加班那味道，但我所要表明的是可以做的事情多了，意味着你的工作量增加了，同时你的编码价值也就提升了，不过实际价值（工资）的话不一定提升，因为这主要看老板和地区。</p>
<p>怎么理解呢，我举几个例子。</p>
<p>现在大部分的 web 应用都采用前后端分离的形式，但在曾经则是由后端通过模板语言渲染成 HTML 直接返回，通过前后端分离的形式，前端只需要专注页面交互的编写，而后端只需要保证接口可靠性。并且前后端分离也有一个特别显著的特点，能非常有效的节省服务器的资源，原本服务器的渲染动作迁移至客户端来做。也许有人会说，那这样 SEO 优化该怎么办，这不就有了 Next.js、Nuxt.js 这样的 SSR（服务端渲染） 框架，而这些工作，也是前端要做的。</p>
<p>并且越到后面你越能感觉到客户端（前端）远比服务端（后端）做的东西来的多，比如开发阶段的数据 Mock，前端国际化，设计原型等等。之所以会有这种感觉，也许是因为我的大部分开发都是前端，这里你也可以回想当下的工作量与往年相比是多了还是不变。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="技术更新快">技术更新快<a href="https://kuizuo.me/blog/2022-year-end-summary#%E6%8A%80%E6%9C%AF%E6%9B%B4%E6%96%B0%E5%BF%AB" class="hash-link" aria-label="技术更新快的直接链接" title="技术更新快的直接链接">​</a></h3>
<p>前端真的是每隔一段时间，必定会出一些新的技术，也不由让不少前端学习者感叹真的学不动。但伴随着新的技术出现，必然会有新的岗位与机会，这也是我看好前端的一个点。</p>
<p><strong>不过这种机会在国内不太多见，反而在国外特别普遍。</strong> 多的不说，自行体会。</p>
<p>忘记从哪看到的一句话：“前端工程师的一大焦虑：永远能（且必须要）看到大量前沿的技术，但自己手上的活儿跟不上，导致眼高手低。”</p>
<p>不只是前端，做技术这行的，必须要跟着技术发展的角度，并且要时刻关注最新，主流的技术。</p>
<hr>
<p>有关 Web 开发的一些想法与感悟就写到这，其实还有蛮多可写的，比如选择 Vue 与 React？为什么是 JavaScript？有太多自我审问的话题了，不过由于当下时间相对紧迫，有机会的话再续写吧。</p>
<p>综上，也就是我看好 Web 技术的几个点，也回答了我未来的方向。希望这些能够帮助一些不知抉择方向的伙伴。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="在实践中学习">在实践中学习<a href="https://kuizuo.me/blog/2022-year-end-summary#%E5%9C%A8%E5%AE%9E%E8%B7%B5%E4%B8%AD%E5%AD%A6%E4%B9%A0" class="hash-link" aria-label="在实践中学习的直接链接" title="在实践中学习的直接链接">​</a></h2>
<p>回顾整个技术栈，基本都是在项目实践中不断学习。也许是因为逆向的缘故，因为逆向基本上都是靠实战出来的，导致我的学习路线也趋于实践。</p>
<p>不过在我看来，写项目是最直接能体现出所想学的技术。我在学习一门新技术时，我通常会用项目或者博文的形式来总结我的学习过程。我也乐意花时间在这上面，并将其分享出来。当有其他人也在学习这门技术时，看到该项目或博文，我就认为非常有意义。</p>
<p><strong>我在学任何一门技术，会使用该技术写点东西；换言之是为了写点东西，而去学点不一样技术。</strong></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="技术心态的变化">技术心态的变化<a href="https://kuizuo.me/blog/2022-year-end-summary#%E6%8A%80%E6%9C%AF%E5%BF%83%E6%80%81%E7%9A%84%E5%8F%98%E5%8C%96" class="hash-link" aria-label="技术心态的变化的直接链接" title="技术心态的变化的直接链接">​</a></h2>
<p>如今来看，确实没那么想写代码，尤其是那种工作量大，重复性强的代码，而编写这些代码，其实与搬砖无任何区别，本质也是重复性与劳动性的任务。</p>
<p>曾经可能是因为接触得比较少，对于很多软件背后的原理及实现一概不知，所以看到啥就都想写写看，最终很多项目都成为半成品。</p>
<p>而随着越深入的学习，反而自己所想实现的东西，前人都已经帮我们实现好了。完全可以借用前人的代码，在此基础上学习与使用，而不必从头再来，耗费很多不必要的时间。</p>
<p>到最后为了实现一开始所想实现的东西，不用再费尽心思，去开源社区搜寻一番，总能找到与自己所要实现的类似的产品，此时只需要会看，会改，总能达到自己最终要实现的东西，有时候自己也称为了别人口中的 CV 工程师。</p>
<p>也正因如此，写代码的欲望就不再那么强烈。很多自认为无意义的代码或者是以后都不一定用得上的代码就少写了许多。于是乎缺少了很多自我思考与专研的时间，虽说也确实节省了很多不必要的时间，加快最终功能的实现，但这就是自己想要的编程生活吗？或者说这就是绝大多数人的编程生活。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="何来的自驱力">何来的自驱力<a href="https://kuizuo.me/blog/2022-year-end-summary#%E4%BD%95%E6%9D%A5%E7%9A%84%E8%87%AA%E9%A9%B1%E5%8A%9B" class="hash-link" aria-label="何来的自驱力的直接链接" title="何来的自驱力的直接链接">​</a></h2>
<p>现在回想，是什么驱使我学习，我心中的答案是无能。</p>
<p>当你什么都不会时，或者是目前的能力还不足够将某件事情做好，你就会不断地焦虑，犹如热锅上的蚂蚁，想做但又怕做不好。</p>
<p>我有很多社会技能是欠缺的，比如人际交往，谈话技巧，合作行为等等，并且我本身也不愿去学习这些技能。正因如此，只有不断发挥自身长处，用自己所擅长的领域弥补自己的短板，以提升自我在社会的竞争力，以至于不那么容易被淘汰。</p>
<p>要让自己保持每天都处于学习状态真的太难了 😩，生活总有源源不断的琐事打扰着你。在忙碌的时候总是感觉时间不够用，哪怕有时一天花费了数十个小时，也总感觉做的事情太少了；哪怕每天计划都规划得好好的，但总是有一半还未完成。生活中大量的碎片化时间，而编程学习最不需要的就是这些碎片化时间。</p>
<p>回顾整个编程生涯有太多可感慨的，整个旅途几乎是一个人走完的，期间遇到的坎坷就只得依靠搜索引擎来解决，搜索引擎是我再生父母都不为过。没有人给我指引明确的道路，只依靠心中对技术的憧憬不断前行。走过低谷，登过山峰，而如今站稳身子就足矣。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="返校的一年">返校的一年<a href="https://kuizuo.me/blog/2022-year-end-summary#%E8%BF%94%E6%A0%A1%E7%9A%84%E4%B8%80%E5%B9%B4" class="hash-link" aria-label="返校的一年的直接链接" title="返校的一年的直接链接">​</a></h2>
<p>今年也是我重归学校的一年，倘若，去年没有休学一年的话，如今我可能已经在外实习了，转瞬一年就过去了，如今的我还沉浸在当初休学和别人创业工作的日子。</p>
<p>也是因为当时休学急迫，后事做得并未完善，学校的一些课程并未申请缓考，最终视为缺考，即挂科。然后此前对大学的课程也是抱着可有可无的心态，也挂了几门课程，如果不是辅导员告知，我还有 34 学分要补（20 学分为严重预警，40 学分为退学或降级处理），我都不曾了解自己在学校挂了这么多科 😂。最离谱的是有个同班同学正好就被降级处理了，不过好在今年上半年并未挂科，加上下半年也重修了一些课程，还不至于离本科毕业证书越走越远。</p>
<p>回到学校后，就感觉如同坐牢一般，只不过环境相对舒适一番，尤其是在疫情当下更是如此，我这一年出校玩乐的次数好像不超过 10 次。当我仔细回想一下大学的真正意义是什么？貌似就是混个日子，混个证书。我也想不出大学能够有什么实质性的作用，也许是因为我所处的学校不行，换个好一点的大学或许都不是这样了。</p>
<p>不过今年回学校反而去参加了曾经都不怎么看上的社团（虽然现在也差不了多少），给社团写了些项目，主要也为了给自己重返学校带点个人知名度。但因为学校不是那么有名（臭名可能有），所以这里就不便放上相关信息。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="考研">考研<a href="https://kuizuo.me/blog/2022-year-end-summary#%E8%80%83%E7%A0%94" class="hash-link" aria-label="考研的直接链接" title="考研的直接链接">​</a></h3>
<p>明年的话我就大三下了，面对我的有条熟知的考研路。说实话，从目前来看，我对考研不抱有太大的希望，我是属于实践派的那种，我坚信没有什么是试不出来。背理论，刷八股，我很反感。</p>
<p>不知道由于什么原因，总感觉一段时间不写代码，真的就不会写代码。仿佛与自己写的代码成为了陌生人一般（所以写注释的重要性就体现出来了）</p>
<p>倘若读研的话，必然将会有数个月的时间重心不在代码上，到时候回来编写代码时，又发现自己好像又重新学了一遍似的。加之万一上岸失败的心理落差，会让自我觉得这几个月的努力都白费。而我又恰好是一位结果论者，即只在意结果，不在意过程如何。在别人的眼中，是难以看到你考研备战努力的过程，只会在意你上没上岸。这便是我不想考研因素之一。</p>
<p>但今年在互联网上所认识伙伴中挺多给我过于高的评价，都认为我作为一个学生，能有这水平是不是在某某牛逼的高校读书。然而并不是，我甚至都不好意思述说自己的高校，说是我的污点也不为过。也正因此身份上，这不得让我萌生读研或是留学的一丝丝想法，也不至于死绝。</p>
<p>自我认为离真正的大佬还差很长的一段距离，我只是靠着三分钟热度才维系下去，而那些真正的大佬是肯花费大量时间去编写他们所认为有意义的代码。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="结语">结语<a href="https://kuizuo.me/blog/2022-year-end-summary#%E7%BB%93%E8%AF%AD" class="hash-link" aria-label="结语的直接链接" title="结语的直接链接">​</a></h2>
<p>今年的年终就告一段落，与之前的年终总结相比，篇幅缩减了许多，主要感觉之前写的挺多流水账的，废话挺多的。也有一点是因为时间相对紧迫，其实 12 月我还没放假，都处于线上上课，线上考试的状态，而线上考试的方式我就不得吐槽了，提前打印给定的答题纸，题目以电脑的方式展示，最终将答案手写到答题纸上并通过手机扫描（扫描全能王）成 PDF 的格式提交，当然摄像头必然也是有的。总之麻烦事还是有的，还没到彻底闲下来闭关学习的时候。</p>
<p>按往常可能还有明年的规划，但我现在认为很多时候都难以依照自身意愿做事，而规划正好自己所设想美好的计划，很容易事与愿违。因为回顾过往的规划，有太多美好的计划，但在实际分析下也将变得不堪一击。不止项目需要可行性分析，人生计划同样需要，一些天马行空幻想，其实就不再有意义去记录了。</p>
<p><strong>坚持做自己想做的事情，而不是逼自己做不愿做的事情</strong>。这是我编程学习中座右铭，也是给予他人编程学习的建议。</p>
<p align="right">写于2022年12月22日    By 愧怍</p>]]></content>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="年终总结" term="年终总结"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[GraphQL 实践与服务搭建]]></title>
        <id>https://kuizuo.me/blog/graphql-practice</id>
        <link href="https://kuizuo.me/blog/graphql-practice"/>
        <updated>2022-11-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[有关 GraphQL 介绍及上手实践，并在 Nest.js 和 Strapi 中搭建 GraphQL 服务]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述，使得客户端能够准确地获得它需要的数据，而且没有任何冗余，也让 API 更容易地随着时间推移而演进，还能用于构建强大的开发者工具。</p>
</blockquote>
<p>大概率你听说过 GraphQL，知道它是一种与 Rest API 架构属于 API 接口的查询语言。但大概率你也与我一样没有尝试过 GraphQL。</p>
<p>事实上从 2012 年 Facebook 首次将 GraphQL 应用于移动应用，到 GraphQL 规范于 2015 年实现开源。可如今现状是 GraphQL 不温不火，时不时又有新的文章介绍，不知道的还以为是什么新技术。</p>
<div class="theme-admonition theme-admonition-tip admonition_lhEC alert alert--success"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p>本文将上手使用 GraphQL，并用 Nestjs 与 Strapi 这两个 Node 框架搭建 GraphQL 服务。</p></div></div>
<p>关于 GraphQL 介绍，详见官网 <a href="https://graphql.cn/" target="_blank" rel="noopener noreferrer" title="GraphQL | A query language for your API">GraphQL | A query language for your API</a> 或相关介绍视频 <a href="https://www.bilibili.com/video/BV1fM4y1A7U1/" target="_blank" rel="noopener noreferrer" title="GraphQL 速览：React/Vue 的最佳搭档">GraphQL 速览：React/Vue 的最佳搭档</a></p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="graphql-与-restful-api-相比">GraphQL 与 Restful API 相比<a href="https://kuizuo.me/blog/graphql-practice#graphql-%E4%B8%8E-restful-api-%E7%9B%B8%E6%AF%94" class="hash-link" aria-label="GraphQL 与 Restful API 相比的直接链接" title="GraphQL 与 Restful API 相比的直接链接">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/9a7412200a062646b729c8419be28b35.jpeg" alt="" class="img_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="restful-api">Restful API<a href="https://kuizuo.me/blog/graphql-practice#restful-api" class="hash-link" aria-label="Restful API的直接链接" title="Restful API的直接链接">​</a></h3>
<p>Restful 架构的设计范式侧重于分配 HTTP 请求方法（GET、POST、PUT、PA TCH、DELETE）和 URL 端点之间的关系。如下图</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/17fc41e2de8d829dc2d41e31a0775df3.png" alt="" class="img_c29J"></p>
<p>但是实际复杂的业务中，单靠 Restful 接口，需要发送多条请求，例如获取博客中某篇博文数据与作者数据</p>
<div class="language-http 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-http 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">GET /blog/1</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">GET /blog/1/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>getBlogAndAuthor</code>，这样直接为调用方“定制”一个接口，请求一条就得到就调用方想要的数据。但是另写一个<code>getBlogAndAuthor</code> 就破坏了 Restful API 接口风格，并且在复杂的业务中，比如说还要获取博文的评论等等，后端就要额外提供一个接口，可以说非常繁琐了。</p>
<p>有没有这样一个功能，将这些接口做一下聚合，然后<strong>将结果的集合返回给前端</strong>呢？在目前比较流行微服务架构体系下，有一个专门的中间层专门来处理这个事情，这个中间层叫 BFF（Backend For Frontend）。可以参阅 <a href="https://blog.csdn.net/qianduan666a/article/details/107271974" target="_blank" rel="noopener noreferrer" title="BFF——服务于前端的后端">BFF——服务于前端的后端</a></p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/Y4u9tNpZwR.png" alt="" class="img_c29J"></p>
<p>但这些接口一般来说都比较重，里面有很多当前页面并不需要的字段，那还有没有一种请求：<strong>客户端只需要发送一次请求就能获取所需要的字段</strong></p>
<p>有，也就是接下来要说的 GraphQL</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="graphql">GraphQL<a href="https://kuizuo.me/blog/graphql-practice#graphql" class="hash-link" aria-label="GraphQL的直接链接" title="GraphQL的直接链接">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/8a141ec5fa73781d66fb2e1b60f9b49d.jpg" alt="" class="img_c29J"></p>
<p>REST API 构建在请求方法（method）和端点（endpoint）之间的连接上，而 GraphQL API 被设计为只通过一个端点，即 <code>/graphql</code>，始终使用 POST 请求进行查询，其集中的 API 如 <a href="http://localhost:3000/graphql%EF%BC%8C%E6%89%80%E6%9C%89%E7%9A%84%E6%93%8D%E4%BD%9C%E9%83%BD%E9%80%9A%E8%BF%87%E8%BF%99%E4%B8%AA%E6%8E%A5%E5%8F%A3%E6%9D%A5%E6%89%A7%E8%A1%8C%EF%BC%8C%E8%BF%99%E4%BC%9A%E5%9C%A8%E5%90%8E%E9%9D%A2%E7%9A%84%E6%93%8D%E4%BD%9C%E4%B8%AD%E5%9C%A8%E5%B1%95%E7%A4%BA%E5%88%B0%E3%80%82" target="_blank" rel="noopener noreferrer">http://localhost:3000/graphql，所有的操作都通过这个接口来执行，这会在后面的操作中在展示到。</a></p>
<div class="theme-admonition theme-admonition-info admonition_lhEC alert alert--info"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p>但是想要一条请求就能得到客户端想要的数据字段，那么服务端必然要做比较多的任务 😟（想想也是，后端啥都不干，前端就啥都能获取，怎么可能嘛）。</p><p>而服务端要做的就是搭建一个 GraphQL 服务，这在后面也会操作到，也算是本文的重点。</p></div></div>
<p>接下来便会在客户端中体验下 GraphQL，看看 GraphQL 究竟有多好用。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="在线体验-graphql"><strong>在线体验 GraphQL</strong><a href="https://kuizuo.me/blog/graphql-practice#%E5%9C%A8%E7%BA%BF%E4%BD%93%E9%AA%8C-graphql" class="hash-link" aria-label="在线体验-graphql的直接链接" title="在线体验-graphql的直接链接">​</a></h2>
<p>可以到 <a href="https://graphql.cn/learn/" target="_blank" rel="noopener noreferrer" title="官网">官网</a> 中简单尝试入门一下，在 <a href="https://studio.apollographql.com/sandbox/explorer" target="_blank" rel="noopener noreferrer" title="Studio (apollographql.com)">Studio</a> 可在线体验 GraphQL，也可以到 <a href="https://swapi-graphql.netlify.app/?query=%7B%20person(personID:%201)%20%7B%20name%20%7D%20%7D" target="_blank" rel="noopener noreferrer" title="SWAPI GraphQL API (swapi-graphql.netlify.app)">SWAPI GraphQL API</a> 中体验。</p>
<p>下面以 <code>apollographql</code> 为例，并查询 People 对象。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="query">query<a href="https://kuizuo.me/blog/graphql-practice#query" class="hash-link" aria-label="query的直接链接" title="query的直接链接">​</a></h3>
<p>查询所有 People 并且只获取 <code>name</code>、<code>gender</code>、<code>height</code> 字段</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/kvWUNtlUbf.png" alt="" class="img_c29J"></p>
<p>查询 personID 为 1 的 Person 并且只获取 <code>name</code>，<code>gender</code>，<code>height</code> 字段</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/Msg9xwWFrl.png" alt="" class="img_c29J"></p>
<p>查询 personID 为 2 的 Person 并且只获取 <code>name</code>，<code>eyeColor</code>、<code>skinColor</code>、<code>hairColor</code> 字段</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/hX0l36Acme.png" alt="" class="img_c29J"></p>
<p>从上面查询案例中其实就可以发现，我只需要在 person 中写上想要获取的字段，GraphQL 便会返回带有该字段的数据。避免了返回结果中不必要的数据字段。</p>
<div class="language-graphql 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-graphql 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 object">person</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span 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 punctuation" style="color:hsl(119, 34%, 47%)">}</span><span 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>如果你不想要 person 数据或者想要其他其他的数据，不用像 Restful API 那样请求多条接口，依旧请求<code>/graphql</code>，如</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/Z0b6ya-auG.png" alt="" class="img_c29J"></p>
<div class="theme-admonition theme-admonition-info admonition_lhEC alert alert--info"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p><strong>无论你想要什么数据，一次请求便可满足。</strong></p></div></div>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="mutation">mutation<a href="https://kuizuo.me/blog/graphql-practice#mutation" class="hash-link" aria-label="mutation的直接链接" title="mutation的直接链接">​</a></h3>
<p>GraphQL 的大部分讨论集中在数据获取（也是它的强项），但是任何完整的数据平台也都需要一个改变服务端数据的方法。即 CRUD。</p>
<p>GraphQL 提供了 <a href="https://graphql.cn/learn/queries/#mutations" target="_blank" rel="noopener noreferrer" title="变更（Mutations）">变更(Mutations)</a> 用于改变服务端数据，不过 <code>apollographql</code> 在线示例中并没有如 <code>createPeople</code> 字段支持 。这个片段在线体验中就无法体验到，后在后文中展示到。这里你只需要知道 GraphQL 能够执行基本的 CRUD 即可。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="fragmen-和-subscribtion">fragmen 和 subscribtion<a href="https://kuizuo.me/blog/graphql-practice#fragmen-%E5%92%8C-subscribtion" class="hash-link" aria-label="fragmen 和 subscribtion的直接链接" title="fragmen 和 subscribtion的直接链接">​</a></h3>
<p>此外还有 <code>fragment </code> 与 <code>subscription</code> 就不做介绍。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="小结">小结<a href="https://kuizuo.me/blog/graphql-practice#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接">​</a></h3>
<p>尝试完上面这些操作后，可以非常明显的感受到 GraphQL 的优势与便利，本来是需要请求不同的 url，现在只需要请求 <code>/graphql</code>，对调用方（前端）来说非常友好，香是真的香。</p>
<p>可目前只是使用了别人配置好的 GraphQL 服务，让前端开发用了特别友好的 API。但是，对于后端开发而言，想要提供 GraphQL 服务可就不那么友善了。因为它不像传统的 restful 请求，需要专门配置 GraphQL 服务，而整个过程是需要花费一定的工作量（定义 Schema，Mutations 等等），前面也提到想要一条请求就能得到客户端想要的数据字段，那服务端必然需要额外的工作量。</p>
<p>不仅需要在后端中配置 GraphQL 服务，用于接收 GraphQL 查询并验证和执行，此外前端通常需要 GraphQL 客户端，来方便使用 GraphQL 获取数据，目前实用比较多的是<a href="https://www.apollographql.com/platform/" target="_blank" rel="noopener noreferrer" title="Apollo Graph">Apollo Graph</a>，不过本文侧重搭建 GraphQL 服务，因此前端暂不演示如何使用 GraphQL。</p>
<p>你可能听过一句话是，<strong>graphq​l 大部分时间在折磨后端</strong>，并且要求比较严格的数据字段，但是好处都是前端。把工作量基本都丢给了后端，所以在遇到使用这门技术的公司，尤其是后端岗位就需要考虑有没有加班的可能了。</p>
<p>以下便会开始实际搭建 GraphQL 服务，这里会用 Nest.js 与 Strapi 分别实践演示。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="nestjs">Nest.js<a href="https://kuizuo.me/blog/graphql-practice#nestjs" class="hash-link" aria-label="Nest.js的直接链接" title="Nest.js的直接链接">​</a></h2>
<p>官方文档：<a href="https://docs.nestjs.com/graphql/quick-start" target="_blank" rel="noopener noreferrer" title="GraphQL + TypeScript | NestJS">GraphQL + TypeScript | NestJS</a></p>
<p>模块：<a href="https://github.com/nestjs/graphql" target="_blank" rel="noopener noreferrer" title="nestjs/graphql">nestjs/graphql</a></p>
<p>仓库本文实例代码仓库： <a href="https://github.com/kuizuo/nest-graphql-demo" target="_blank" rel="noopener noreferrer" title="kuizuo/nest-graphql-demo">kuizuo/nest-graphql-demo</a></p>
<p><strong>创建项目</strong></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">nest new nest-graphql-demo</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><strong>安装依赖</strong></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"> i @nestjs/graphql @nestjs/apollo graphql apollo-server-express</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><strong>修改 app.module.ts</strong></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.module.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"> Module </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"> GraphQLModule </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/graphql'</span><span 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"> ApolloDriver</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> ApolloDriverConfig </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/apollo'</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%)">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"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">    GraphQLModule</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token generic-function function" style="color:hsl(221, 87%, 60%)">forRoot</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%)">ApolloDriverConfig</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">      driver</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> ApolloDriver</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      autoSchemaFile</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">  </span><span class="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%)">AppModule</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>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="resolver">resolver<a href="https://kuizuo.me/blog/graphql-practice#resolver" class="hash-link" aria-label="resolver的直接链接" title="resolver的直接链接">​</a></h3>
<p>设置了<code>autoSchemaFile: true</code> ，nest.js 将会自动搜索整个项目所有以 <code>.resolver.ts</code> 为后缀的文件，将其解析为 <code>schema.gql</code> 比如说创建<code>app.resolver.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>app.resolver.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"> Resolver</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> Query </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/graphql'</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%)">Resolver</span><span class="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%)">AppResolver</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%)">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"> String</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%)">// 定义一个查询,并且返回字符类型</span><span 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%)">hello</span><span class="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 string" style="color:hsl(119, 34%, 47%)">'hello world'</span><span 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>graphql</code> 中 <code>resolver</code> 叫解析器，与 <code>service</code> 类似（也需要在 <code>@Module</code> 中通过 <code>providers</code> 导入）。<code>resolver</code>主要包括<code>query</code>(查询数据)、<code>mutation</code>(增、删、改数据)、<code>subscription</code>(订阅，有点类型 <code>socket</code>)，在 <code>graphql</code> 项目中我们用 <code>resolver</code> 替换了之前的控制器。</p>
<p>这时候打开<a href="http://127.0.0.1:3000/graphql" target="_blank" rel="noopener noreferrer" title="http://127.0.0.1:3000/graphql">http://127.0.0.1:3000/graphql</a>，可以在右侧中看到自动生成的 Schema，这个 Schema 非常关键，决定了你客户端能够请求到什么数据。</p>
<p>尝试输入 GraphQL 的 query 查询（可以按 Ctrl + i 触发代码建议（Trigger Suggest），与 vscode 同理）</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/a3yl4oVtSU.png" alt="" class="img_c29J"></p>
<p>此时点击执行，可以得到右侧结果，即<code>app.resolver.ts</code> 中 <code>hello</code> 函数所定义的返回体。</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/bK9bvZ3QMm.png" alt="" class="img_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="code-first-与-schema-first"><a href="https://docs.nestjs.com/graphql/quick-start#code-first" target="_blank" rel="noopener noreferrer">Code first</a> 与 <a href="https://docs.nestjs.com/graphql/quick-start#schema-first" target="_blank" rel="noopener noreferrer">Schema first</a><a href="https://kuizuo.me/blog/graphql-practice#code-first-%E4%B8%8E-schema-first" class="hash-link" aria-label="code-first-与-schema-first的直接链接" title="code-first-与-schema-first的直接链接">​</a></h3>
<p>在 nestjs 中有 <a href="https://docs.nestjs.com/graphql/quick-start#code-first" target="_blank" rel="noopener noreferrer">Code first</a> 与 <a href="https://docs.nestjs.com/graphql/quick-start#schema-first" target="_blank" rel="noopener noreferrer">Schema first</a> 两种方式来生成上面的 Schema，从名字上来看，前者是优先定义代码会自动生成 Schema，而后者是传统方式先定义 Schema。</p>
<p>在上面一开始的例子中是 Code First 方式，通常使用该方式即可，无需关心 Schema 是如何生成的。下文也会以 Code First 方式来编写 GraphQL 服务。</p>
<p>也可到官方示例仓库中 <a href="https://github.com/nestjs/nest/tree/master/sample/31-graphql-federation-code-first" target="_blank" rel="noopener noreferrer">nest/sample/31-graphql-federation-code-first</a> 和 <a href="https://github.com/nestjs/nest/tree/master/sample/32-graphql-federation-schema-first" target="_blank" rel="noopener noreferrer">nest/sample/32-graphql-federation-schema-first</a> 查看两者代码上的区别。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="快速生成-graphql-模块">快速生成 GraphQL 模块<a href="https://kuizuo.me/blog/graphql-practice#%E5%BF%AB%E9%80%9F%E7%94%9F%E6%88%90-graphql-%E6%A8%A1%E5%9D%97" class="hash-link" aria-label="快速生成 GraphQL 模块的直接链接" title="快速生成 GraphQL 模块的直接链接">​</a></h3>
<p>nest 提供 cli 的方式来快速生成 GraphQL 模块</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">nest g resource </span><span class="token operator" style="color:hsl(221, 87%, 60%)">&lt;</span><span class="token plain">name</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><img decoding="async" loading="lazy" src="https://img.kuizuo.me/L9yYAn78Dw.png" alt="" class="img_c29J"></p>
<p>比如创建一个 blog 模块</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">nest g resource blog --no-spec</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">? What transport layer </span><span class="token keyword" style="color:hsl(301, 63%, 40%)">do</span><span class="token plain"> you use? GraphQL </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">code first</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">? Would you like to generate CRUD entry points? Yes</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">CREATE src/blog/blog.module.ts </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">217</span><span class="token plain"> bytes</span><span class="token punctuation" style="color:hsl(119, 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 src/blog/blog.resolver.ts </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">1098</span><span class="token plain"> bytes</span><span class="token punctuation" style="color:hsl(119, 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 src/blog/blog.resolver.spec.ts </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">515</span><span class="token plain"> bytes</span><span class="token punctuation" style="color:hsl(119, 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 src/blog/blog.service.ts </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">623</span><span class="token plain"> bytes</span><span class="token punctuation" style="color:hsl(119, 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 src/blog/blog.service.spec.ts </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">446</span><span class="token plain"> bytes</span><span class="token punctuation" style="color:hsl(119, 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 src/blog/dto/create-blog.input.ts </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">196</span><span class="token plain"> bytes</span><span class="token punctuation" style="color:hsl(119, 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 src/blog/dto/update-blog.input.ts </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">243</span><span class="token plain"> bytes</span><span class="token punctuation" style="color:hsl(119, 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 src/blog/entities/blog.entity.ts </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">187</span><span class="token plain"> bytes</span><span class="token punctuation" style="color:hsl(119, 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 src/app.module.ts </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token number" style="color:hsl(35, 99%, 36%)">643</span><span class="token plain"> bytes</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/XemqTcfz_D.png" alt="" class="img_c29J"></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>blog.resolver.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"> Resolver</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> Query</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"> Args</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> Int </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/graphql'</span><span 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"> BlogService </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%)">'./blog.service'</span><span 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"> Blog </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%)">'./entities/blog.entity'</span><span 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"> CreateBlogInput </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%)">'./dto/create-blog.input'</span><span 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"> UpdateBlogInput </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%)">'./dto/update-blog.input'</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%)">Resolver</span><span 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"> 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 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%)">BlogResolver</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"> blogService</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> BlogService</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 decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Mutation</span><span 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"> 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 function" style="color:hsl(221, 87%, 60%)">createBlog</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%)">Args</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'createBlogInput'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> createBlogInput</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> CreateBlogInput</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">blogService</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">createBlogInput</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span 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%)">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 punctuation" style="color:hsl(119, 34%, 47%)">[</span><span class="token plain">Blog</span><span class="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"> 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%)">'blogs'</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 function" style="color:hsl(221, 87%, 60%)">findAll</span><span class="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">blogService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">findAll</span><span class="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 decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator 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"> Blog</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"> 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%)">'blog'</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 function" style="color:hsl(221, 87%, 60%)">findOne</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%)">Args</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><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">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 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"> 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"> 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 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">blogService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" 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 decorator at operator" style="color:hsl(221, 87%, 60%)">@</span><span class="token decorator function" style="color:hsl(221, 87%, 60%)">Mutation</span><span 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"> 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 function" style="color:hsl(221, 87%, 60%)">updateBlog</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%)">Args</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token string" style="color:hsl(119, 34%, 47%)">'updateBlogInput'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token plain"> updateBlogInput</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> UpdateBlogInput</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">blogService</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">updateBlogInput</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"> updateBlogInput</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span 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%)">Mutation</span><span 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"> 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 function" style="color:hsl(221, 87%, 60%)">removeBlog</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%)">Args</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><span class="token punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token plain"> </span><span class="token function-variable function" style="color:hsl(221, 87%, 60%)">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 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"> 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"> 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 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">blogService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">remove</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"></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>此时 Schema 如下</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/sJCQpllOXK.png" alt="" class="img_c29J"></p>
<p>不过<code>nest cli</code>创建的<code>blog.service.ts</code> 只是示例代码，并没有实际业务的代码。</p>
<p>此外<code>blog.entity.ts</code>也不为数据库实体类，因此这里引入<code>typeorm</code>，并使用<code>sqlite3</code></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="集成-typeorm">集成 Typeorm<a href="https://kuizuo.me/blog/graphql-practice#%E9%9B%86%E6%88%90-typeorm" class="hash-link" aria-label="集成 Typeorm的直接链接" title="集成 Typeorm的直接链接">​</a></h3>
<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%)">pnpm</span><span class="token plain"> </span><span class="token function" style="color:hsl(221, 87%, 60%)">install</span><span class="token plain"> @nestjs/typeorm typeorm sqlite3</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>app.module.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"> Module </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"> AppController </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%)">'./app.controller'</span><span 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"> AppService </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%)">'./app.service'</span><span 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"> GraphQLModule </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/graphql'</span><span 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"> ApolloDriver</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> ApolloDriverConfig </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/apollo'</span><span 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"> AppResolver </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%)">'./app.resolver'</span><span 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"> BlogModule </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%)">'./blog/blog.module'</span><span 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"> TypeOrmModule </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/typeorm'</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%)">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"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><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%)">forRoot</span><span class="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 operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> </span><span class="token string" style="color:hsl(119, 34%, 47%)">'sqlite'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      database</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%)">'db.sqlite3'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      entities</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">__dirname </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%)">'/**/*.entity{.ts,.js}'</span><span class="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">      synchronize</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">    GraphQLModule</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token generic-function function" style="color:hsl(221, 87%, 60%)">forRoot</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%)">ApolloDriverConfig</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">      driver</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> ApolloDriver</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">      autoSchemaFile</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">      playground</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">    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">    BlogModule</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span 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">  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">AppController</span><span class="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">AppService</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> AppResolver</span><span class="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%)">AppModule</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>blog.entity.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>blog.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 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"> ObjectType</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> Field </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/graphql'</span><span 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"> Column</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> Entity</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> PrimaryGeneratedColumn</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> CreateDateColumn</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> UpdateDateColumn </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%)">'typeorm'</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%)">ObjectType</span><span class="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%)">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%)">Blog</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%)">Field</span><span 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"> Int</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span 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%)">Field</span><span class="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><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token plain">  title</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%)">Field</span><span class="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"> 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 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">  content</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%)">Field</span><span class="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%)">CreateDateColumn</span><span class="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%)">'created_at'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> comment</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 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">  createdAt</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Date</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%)">Field</span><span class="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%)">UpdateDateColumn</span><span class="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%)">'updated_at'</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> comment</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 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">  updatedAt</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> Date</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>@ObjectType()</code> 装饰器让 <code>@nestjs/graphql</code> 自动让其视为一个 <code>type Blog</code></p>
<p>而 <code>@Field()</code> 则是作为可展示的字段，比如 <code>password</code> 字段无需返回，就不必要加该装饰器。</p>
<div class="theme-admonition theme-admonition-tip admonition_lhEC alert alert--success"><div class="admonitionHeading_O9Fl"><span class="admonitionIcon_evF6"><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_h773"><p>如果你认为 添加 <code>@Field()</code> 是件繁琐的事情（nest 官方自然也想到），于是提供了 <a href="https://docs.nestjs.com/graphql/cli-plugin" target="_blank" rel="noopener noreferrer">GraphQL + TypeScript - CLI Plugin </a> 用于省略 <code>@Field()</code> 等其他操作。（类似于语法糖）</p><p>借用官方的话:</p><blockquote>
<p>Thus, you won't have to struggle with @Field decorators scattered throughout the code.</p>
</blockquote><p>因此，您不必为分散在代码中的 <code>@Field</code> 装饰符而烦恼。</p></div></div>
<p><code>@nestjs/graphql</code> 会将 typescript 的 number 类型视为 Float，所以需要转成 Int 类型，即 <code>@Field(() =&gt; Int)</code></p>
<p>在 BlogService 编写 CRUD 数据库业务代码，并在 dto 编写参数效验代码，这里简单暂时部分代码。</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>blog.service.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"> 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"> InjectRepository </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/typeorm'</span><span 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"> Repository </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%)">'typeorm'</span><span 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"> CreateBlogInput </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%)">'./dto/create-blog.input'</span><span 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"> UpdateBlogInput </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%)">'./dto/update-blog.input'</span><span 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"> Blog </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%)">'./entities/blog.entity'</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%)">BlogService</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 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%)">InjectRepository</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">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 keyword" style="color:hsl(301, 63%, 40%)">private</span><span class="token plain"> blogRepository</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">Blog</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 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%)">create</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token plain">createBlogInput</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> CreateBlogInput</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">blogRepository</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">createBlogInput</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span 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%)">findAll</span><span class="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">blogRepository</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">find</span><span class="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%)">findOne</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 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">blogRepository</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">findOneBy</span><span class="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 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%)">async</span><span class="token plain"> </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 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"> updateBlogInput</span><span class="token operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token plain"> UpdateBlogInput</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"> blog </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">blogRepository</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token function" style="color:hsl(221, 87%, 60%)">findOneBy</span><span class="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 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"> item </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><span class="token operator" style="color:hsl(221, 87%, 60%)">...</span><span class="token plain">blog</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">updateBlogInput </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span 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">blogRepository</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">item</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">)</span><span 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%)">remove</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 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">blogRepository</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">  </span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">}</span><span 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>create-blog.input.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"> InputType</span><span class="token punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token plain"> Field </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/graphql'</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%)">InputType</span><span class="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%)">CreateBlogInput</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%)">Field</span><span class="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">  title</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%)">Field</span><span class="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">  content</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>此时</p>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/7-twN56Aym.png" alt="" class="img_c29J"></p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="crud">CRUD<a href="https://kuizuo.me/blog/graphql-practice#crud" class="hash-link" aria-label="CRUD的直接链接" title="CRUD的直接链接">​</a></h3>
<p>下面将演示 graphql 的 Mutation。</p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="新增">新增<a href="https://kuizuo.me/blog/graphql-practice#%E6%96%B0%E5%A2%9E" class="hash-link" aria-label="新增的直接链接" title="新增的直接链接">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/NPqShDN3Pl.png" alt="" class="img_c29J"></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="修改">修改<a href="https://kuizuo.me/blog/graphql-practice#%E4%BF%AE%E6%94%B9" class="hash-link" aria-label="修改的直接链接" title="修改的直接链接">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/c4ycwRs-po.png" alt="" class="img_c29J"></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="删除">删除<a href="https://kuizuo.me/blog/graphql-practice#%E5%88%A0%E9%99%A4" class="hash-link" aria-label="删除的直接链接" title="删除的直接链接">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/xpkHhpS1-K.png" alt="" class="img_c29J"></p>
<p>Query 就不在演示。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="小结-1">小结<a href="https://kuizuo.me/blog/graphql-practice#%E5%B0%8F%E7%BB%93-1" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接">​</a></h3>
<p>至此，在 Nest.js 中配置 GraphQL 服务的就演示到此，从这里来看，Nest.js 配置 GraphQL 服务还算比较轻松，但是做了比较多的工作量，创建 resolver，创建 modal（或在已有实体添加装饰器），不过本文案例中只演示了基本的 CRUD 操作，实际业务中还需要涉及鉴权，限流等等。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="strapi">Strapi<a href="https://kuizuo.me/blog/graphql-practice#strapi" class="hash-link" aria-label="Strapi的直接链接" title="Strapi的直接链接">​</a></h2>
<p>Strapi 官方提供 <a href="https://market.strapi.io/plugins/@strapi-plugin-graphql" target="_blank" rel="noopener noreferrer" title="GraphQL插件">GraphQL 插件</a> 免去了配置的繁琐。更具体的配置参见 <a href="https://docs.strapi.io/developer-docs/latest/development/plugins/graphql.html" target="_blank" rel="noopener noreferrer" title="GraphQL - Strapi Developer Documentation">GraphQL - Strapi Developer Documentation</a></p>
<p>这里我就选用 <a href="https://github.com/kuizuo/vitesse-nuxt-strapi" target="_blank" rel="noopener noreferrer" title="kuizuo/vitesse-nuxt-strapi">kuizuo/vitesse-nuxt-strapi</a> 作为演示，并为其提供 graphQL 支持。</p>
<p>strapi 安装</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"> @strapi/plugin-graphql</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>接着启动 strapi 项目，并在浏览器打开 graphql 控制台 <a href="http://localhost:1337/graphql" target="_blank" rel="noopener noreferrer" title="http://localhost:1337/graphql">http://localhost:1337/graphql</a>，以下将演示几个应用场景。</p>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="例子">例子<a href="https://kuizuo.me/blog/graphql-practice#%E4%BE%8B%E5%AD%90" class="hash-link" aria-label="例子的直接链接" title="例子的直接链接">​</a></h3>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="查询所有-todo">查询所有 todo<a href="https://kuizuo.me/blog/graphql-practice#%E6%9F%A5%E8%AF%A2%E6%89%80%E6%9C%89-todo" class="hash-link" aria-label="查询所有 todo的直接链接" title="查询所有 todo的直接链接">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/4GFUs8CmQJ.png" alt="" class="img_c29J"></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="查询-id-为-2-的-todo">查询 id 为 2 的 todo<a href="https://kuizuo.me/blog/graphql-practice#%E6%9F%A5%E8%AF%A2-id-%E4%B8%BA-2-%E7%9A%84-todo" class="hash-link" aria-label="查询 id 为 2 的 todo的直接链接" title="查询 id 为 2 的 todo的直接链接">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/NMM4e3L_y8.png" alt="" class="img_c29J"></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="查询-id-为-2-的-todo-并只返回-value-属性">查询 id 为 2 的 todo 并只返回 value 属性<a href="https://kuizuo.me/blog/graphql-practice#%E6%9F%A5%E8%AF%A2-id-%E4%B8%BA-2-%E7%9A%84-todo-%E5%B9%B6%E5%8F%AA%E8%BF%94%E5%9B%9E-value-%E5%B1%9E%E6%80%A7" class="hash-link" aria-label="查询 id 为 2 的 todo 并只返回 value 属性的直接链接" title="查询 id 为 2 的 todo 并只返回 value 属性的直接链接">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/E1eWrzjaEs.png" alt="" class="img_c29J"></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="新增-todo">新增 todo<a href="https://kuizuo.me/blog/graphql-practice#%E6%96%B0%E5%A2%9E-todo" class="hash-link" aria-label="新增 todo的直接链接" title="新增 todo的直接链接">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/pclR7Zb6TE.png" alt="" class="img_c29J"></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="更新-todo">更新 todo<a href="https://kuizuo.me/blog/graphql-practice#%E6%9B%B4%E6%96%B0-todo" class="hash-link" aria-label="更新 todo的直接链接" title="更新 todo的直接链接">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/g3RJL7RQWR.png" alt="" class="img_c29J"></p>
<h4 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="删除-todo">删除 todo<a href="https://kuizuo.me/blog/graphql-practice#%E5%88%A0%E9%99%A4-todo" class="hash-link" aria-label="删除 todo的直接链接" title="删除 todo的直接链接">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://img.kuizuo.me/m7s17q2TG0.png" alt="" class="img_c29J"></p>
<p>由于 <a href="https://strapi.nuxtjs.org/" target="_blank" rel="noopener noreferrer" title="Nuxt Strapi">Nuxt Strapi</a> 提供 <a href="https://strapi.nuxtjs.org/usage#usestrapigraphql" target="_blank" rel="noopener noreferrer" title="useStrapiGraphQL">useStrapiGraphQL</a> 可以非常方便是在客户端调用 GraphQL 服务。</p>
<div class="language-vue 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-vue 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" style="color:hsl(5, 74%, 59%)">script</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">setup</span><span class="token tag" style="color:hsl(5, 74%, 59%)"> </span><span class="token tag attr-name" style="color:hsl(35, 99%, 36%)">lang</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%)">ts</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 script language-javascript"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript"></span><span class="token script language-javascript keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token script language-javascript"> route </span><span class="token script language-javascript operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript function" style="color:hsl(221, 87%, 60%)">useRoute</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript"></span><span class="token script language-javascript keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token script language-javascript"> graphql </span><span class="token script language-javascript operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript function" style="color:hsl(221, 87%, 60%)">useStrapiGraphQL</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript"></span><span class="token script language-javascript comment" style="color:hsl(230, 4%, 64%)">// Option 1: use inline query</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript"></span><span class="token script language-javascript keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token script language-javascript"> restaurant </span><span class="token script language-javascript operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript keyword control-flow" style="color:hsl(301, 63%, 40%)">await</span><span class="token script language-javascript"> </span><span class="token script language-javascript function" style="color:hsl(221, 87%, 60%)">graphql</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">  query {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">    restaurant(id: </span><span class="token script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">${</span><span class="token script language-javascript template-string interpolation">route</span><span class="token script language-javascript template-string interpolation punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token script language-javascript template-string interpolation property-access">params</span><span class="token script language-javascript template-string interpolation punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token script language-javascript template-string interpolation property-access">id</span><span class="token script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">) {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">      data {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">        id</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">        attributes {</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">          name</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">        }</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">      }</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">    }</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)">  }</span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript template-string string" style="color:hsl(119, 34%, 47%)"></span><span class="token script language-javascript template-string template-punctuation string" style="color:hsl(119, 34%, 47%)">`</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript" style="display:inline-block"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript"></span><span class="token script language-javascript comment" style="color:hsl(230, 4%, 64%)">// Option 2: use imported query</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript"></span><span class="token script language-javascript keyword" style="color:hsl(301, 63%, 40%)">const</span><span class="token script language-javascript"> restaurant </span><span class="token script language-javascript operator" style="color:hsl(221, 87%, 60%)">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript keyword control-flow" style="color:hsl(301, 63%, 40%)">await</span><span class="token script language-javascript"> </span><span class="token script language-javascript function" style="color:hsl(221, 87%, 60%)">graphql</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">(</span><span class="token script language-javascript">query</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">,</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">{</span><span class="token script language-javascript"> </span><span class="token script language-javascript literal-property property" style="color:hsl(5, 74%, 59%)">id</span><span class="token script language-javascript operator" style="color:hsl(221, 87%, 60%)">:</span><span class="token script language-javascript"> route</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token script language-javascript property-access">params</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">.</span><span class="token script language-javascript property-access">id</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">}</span><span class="token script language-javascript punctuation" style="color:hsl(119, 34%, 47%)">)</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:hsl(230, 8%, 24%)"><span class="token script language-javascript"></span><span class="token tag punctuation" style="color:hsl(119, 34%, 47%)">&lt;/</span><span class="token tag" style="color:hsl(5, 74%, 59%)">script</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>
<h3 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="小结-2">小结<a href="https://kuizuo.me/blog/graphql-practice#%E5%B0%8F%E7%BB%93-2" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接">​</a></h3>
<p>对于 Strapi 来说，搭建 GraphQL 服务基本没有配置的负担，安装一个插件，即可配合 Strapi 的 content-type 来提供 GraphQL 服务。</p>
<h2 class="anchor anchorWithHideOnScrollNavbar_vMh4" id="总结">总结<a href="https://kuizuo.me/blog/graphql-practice#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接">​</a></h2>
<p><strong>GraphQL</strong> 翻译过来为 <strong>图表 Query Language</strong>，我所理解的理念是通过 json 数据格式的方式去写 SQL，而且有种前端人员在写 sql 语句。在我看来 GraphQL 更多是业务数据特别复制的情况下使用，往往能够事半功倍。但对于本文中示例的代码而言，GraphQL 反倒有点过于先进了。</p>
<p>如今看来，GraphQL 还处于不温不火的状态，目前更多的站点主流还是使用 Restful API 架构。我不过我猜测，主要还是大多数业务没有 API 架构的升级的需求，原有的 Restful API 虽说不够优雅，但是也能够满足业务的需求，反而 GraphQL 是一个新项目 API 架构的选择，但不是一个必须的选择。</p>
<p>至于如何选择，可以参阅官方 <a href="https://graphql.cn/learn/best-practices/" target="_blank" rel="noopener noreferrer">GraphQL 最佳实践</a>，至于说有没有必要学 GraphQL，这篇文章 <a href="https://blog.csdn.net/kevin_tech/article/details/120735500" target="_blank" rel="noopener noreferrer">都快 2022 年了 GraphQL 还值得学吗</a> 能给你答案。我的建议是了解即可，新项目可以考虑使用，就别想着用 GraphQL 来重构原有的 API 接口，工作量将会十分巨大，并且还可能是费力不讨好的事。反正我认为这门技术不像 Git 这种属于必学的技能，我的五星评分是 ⭐⭐</p>
<p>但多了解一门技术，就是工作面试的资本。回想我为何尝试 GraphQL，就是因为我无意间看到了一份 ts 全栈的远程面试招聘，在这份招聘单中写到 【会 graphql 编写是加分项】。所以抱着这样的态度去尝试了一番，说不准未来就是因为 graphql 让我拿到该 offer。当然也是因为很早之前就听闻 GraphQL，想亲手目睹下是否有所谓的那么神奇。</p>]]></content>
        <author>
            <name>愧怍</name>
            <email>hi@kuizuo.me</email>
            <uri>https://github.com/kuizuo</uri>
        </author>
        <category label="api" term="api"/>
        <category label="graphql" term="graphql"/>
        <category label="nest" term="nest"/>
        <category label="strapi" term="strapi"/>
    </entry>
</feed>