<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>AaronConlon's Blog</title>
        <link>https://i5lin.top/</link>
        <description>AaronConlon's Blog</description>
        <lastBuildDate>Wed, 09 Sep 2026 21:53:10 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Feed for Node.js</generator>
        <language>zh-CN</language>
        <image>
            <title>AaronConlon's Blog</title>
            <url>https://i5lin.top/coder3.svg</url>
            <link>https://i5lin.top/</link>
        </image>
        <copyright>All rights reserved 2024, Blog</copyright>
        <atom:link href="https://i5lin.top/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[GitHub 故障 7 小时复盘：最隐蔽的重试风暴]]></title>
            <link>https://i5lin.top/blog/post/5200620824</link>
            <guid isPermaLink="false">5200620824</guid>
            <pubDate>Thu, 20 Aug 2026 06:57:58 GMT</pubDate>
            <description><![CDATA[从一次提交代码时的真实翻车现场切入，带你读懂 GitHub 官方事故复盘，拆解代理容量瓶颈与客户端重试如何层层放大系统故障。]]></description>
            <content:encoded><![CDATA[<p>hello！大家好！</p>
<p>上周五晚上九点半左右，我坐在电脑前正准备把一个功能分支合并进主分支。</p>
<p>在网页上点下 Merge 后，浏览器顶部那条蓝色的进度条卡在中间不动了。过了几秒，整页直接跳出一片空白，随后返回了 500 错误。我顺手强制刷新页面，依然无法加载。</p>
<p>第一反应是以为本地网络代理出了问题，我随手新开了一个标签页刷了下某个屎黄色站点，视频秒开，网络非常顺畅。再切回 VS Code，右下角 Copilot 图标也悄悄亮起了无法连接的提示。</p>
<p>这时候在技术交流群里已经有人开始发截图问：“GitHub 是不是挂了？”。然而群里的反馈并不一致：有的朋友说完全打不开，有的朋友却说自己拉取代码一切正常。推特时间线上也有不少开发者陆续晒出 500 报错页面。</p>
<p>大家一边在群里发表情包，一边排查到底发生了什么。这种“部分地区能用、部分地区报错”的半瘫痪状态持续了很久。部分用户的 PR 无法合并、CI/CD 停滞，VS Code 里的 Copilot 也频频断连。</p>
<p>一直到第二天早上，经历了整整 7 个多小时的局部震荡后，GitHub Status 正式发布了事故复盘。报告里记录了这次事故的前因后果。</p>
<p>撇开那些过于晦涩的技术术语，我们用清晰、直白的方式，看看这场局部受损却波及极广的事故到底是怎么发生的。</p>
<p>来源：<a href="https://www.githubstatus.com/incidents/zkxwbgr0cnmx">GitHub Status 事故报告</a></p>
<h2>事故影响：部分服务与区域受阻</h2>
<p>根据 GitHub Status 官方记录（ 8 月 17 日 13:28–21:15），本次事故持续了整整 7 小时 47 分钟。</p>
<p>需要先明确一个关键事实：<strong>本次事故并非全球全站彻底下线，主要受灾区域集中在 Central US 数据中心，表现为部分地区与部分服务的间歇性高错误率。</strong></p>
<p>官方披露的核心受损指标相当直观：</p>
<ul>
<li>网页端与 REST/GraphQL API 整体错误率峰值接近 20%（其余大部分正常流量依然可达）。</li>
<li>仓库 archive 与 raw content 的下载错误率攀升至约 50%。</li>
<li>Issues、Pull Requests、Actions 和 Webhooks 出现大面积降级，Git 操作、Pages 与 Copilot 均受到不同程度冲击。</li>
<li>涉及企业级身份认证的 SAML/OIDC、SCIM 以及 Team Sync 路径同样出现局部故障。</li>
<li>采用 Data Residency 架构的企业客户，由于部分 Actions 工作流依赖 GitHub.com 托管的公开 Step 定义，也遭到级联波及。</li>
<li>与此同时，通过 GitHub CLI 和 GitHub App 使用 Copilot 的用户全程未受影响，受灾最严重的是 VS Code 等桌面客户端。</li>
</ul>
<p>这种“有人正常、有人全挂”的局部异常，反而让排查变得更加扑朔迷离。</p>
<h2>故障根因：容量配置盲区引发连锁阻塞</h2>
<p>梳理官方报告可以发现，整场事故的源头来自一个容易被忽视的网络代理配置盲区：</p>
<ol>
<li><strong>Sidecar 代理触及并发上限</strong>：Central US 数据中心遭遇流量高峰，负责内部网络路由的单个 Istio sidecar pod 达到了并发连接上限。举个例子，就好比一个业务窗口虽然大门敞开，但门前的专用安检通道已经挤满了人，流量大到挤满通道，没法疏导。</li>
<li><strong>自动扩容指标未联动</strong>：自动扩缩容策略（HPA）当时只盯住了宿主业务服务的 CPU 和请求指标，未把 Sidecar 代理的并发负荷算进去。系统误以为容量充足，未及时启动弹性扩容。</li>
<li><strong>关键节点连接耗尽</strong>：局部的网络阻塞迅速扩散，直接导致上游 4 个关键 HAProxy 节点耗尽了连接与流量配额（flow limit）。</li>
<li><strong>认证通路大面积变慢</strong>：HAProxy 承载着网关身份鉴权的核心链路。一旦这里堵死，依赖鉴权验证的网页端、API、Actions 和 Copilot 服务立即陷入大面积延迟和超时。</li>
</ol>
<p>认证网关如同整座系统的总闸机，闸机卡住后，内部即便各模块完好，外部用户也无法正常通行。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/08/8d44090b8af8070ed12ad266c9fc21b8.webp" alt=""></p>
<h2>放大效应：客户端重试引发流量风暴</h2>
<p>如果仅仅是局部网关卡顿，系统或许能较快排查并完成切换。真正让事故拉锯了近 8 个小时的关键，在于各层<strong>重试机制</strong>造成的流量放大。</p>
<p>平时遇到接口报错，大家最下意识的动作是什么？
重试！不行就再点一次！接着连续狂按重试！
大家总觉得“多试一次总能成”，而客户端程序更是在后台不知疲倦地把这个动作放大了成千上万倍。</p>
<p>在大规模故障场景下，未经节制的重试往往会演变成无意识的集群压迫：</p>
<ul>
<li>鉴权与服务端响应延迟飙升。</li>
<li>客户端判定请求超时并自动触发重试。</li>
<li>网关层根据既定策略同样向下游追加重试。</li>
<li>呈几何级数叠加的重试流量，彻底挤占了负载均衡器与认证服务的全部可用带宽。</li>
</ul>
<p>用户在 VS Code 里看到的只是一句“Copilot 登录超时”，而在服务端看来，这就如同几万名用户因为闸机稍慢了一拍，在几秒内疯狂连刷了几十次卡。</p>
<p>官方数据揭示了这一惊人事实：Copilot Token Service 日常请求量为 7–9K RPS，在重试叠加下被暴力推升至 <strong>70–100K RPS</strong>。整整 10 倍的畸形洪峰，直接让正在尝试恢复的服务再次过载。</p>
<p>此外，VS Code 内部存在一个潜伏的重试逻辑缺陷，当某个内部端点响应稍有延迟，编辑器便会进入快速循环补发，成为了阻碍恢复的核心堵点。</p>
<p>系统出现故障时，盲目的重试机制往往比原始缺陷更快拖垮整套架构。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/08/888c3e86676fe53f74fec02c6ba34127.webp" alt=""></p>
<h2>跨区域调度与紧急止血手段</h2>
<p>为了分流压力，GitHub 曾尝试将 Central US 的受阻流量切往 Northern Virginia 数据中心。</p>
<p>然而，跨区域调度只能改变请求的目的地，无法削减总调用量。数以百万计的客户端依然在以极高频率狂发重试，这股被放大了 10 倍的洪峰涌入新机房后，很快又给新的节点带来了沉重负担。</p>
<p>面对不断自我繁殖的恶性流量，GitHub 采取了数项强硬的阻断措施：</p>
<ul>
<li>紧急推送配置，在网关层大幅降低自动重试的频次。</li>
<li>在负载均衡器层对部分 Copilot Token 请求直接返回 403 拒绝，强行截断客户端的重试死循环。</li>
<li>分站点、小批量逐步放行流量，给后端留出充足的缓冲时间。</li>
<li>同步暂停存在异常的 HAProxy 节点并完成状态恢复。</li>
</ul>
<p>把盲目的重试流量完全压制住之后，整个服务集群才终于恢复平稳。</p>
<h2>看完报告的几点体会</h2>
<p>笔者作为每天都在使用 GitHub 的“麻瓜”开发者，读完这篇详实的官方复盘，还是发表一下感触吧：</p>
<ul>
<li><strong>强如行业标杆，也会被微小的配置盲区绊倒</strong>：业务服务与代理容器没有联动扩容、客户端埋着一个偶发的重试死循环，平时风平浪静，一旦遇上业务高峰就会引发连锁反应。</li>
<li><strong>多机房调度并不是万能解药</strong>：面对失控的客户端重试风暴，单纯切机房只是把压力转移到另一个地方，甚至会迅速拖垮健康的备用节点。</li>
<li><strong>详实透明的复盘具有极高价值</strong>：GitHub 没有用一句含糊的“系统维护”带过，而是把完整的故障周期（7 小时 47 分钟）、各业务受损指标（API 20%、下载 50%）、10 倍流量放大数据以及具体的改进举措完整公开。这种复盘不仅能给用户明确的交代，也是极具参考价值的工程教材，这里就不得不提一下国内某大厂，简直是反着来，恶心！</li>
</ul>
<h2>日常开发的避坑建议</h2>
<p>GitHub 的架构规模非常庞大，但这场事故里暴露出的问题，也许在咱们的日常业务开发中同样很常见。结合这次教训，在和 Claude Fable ”老师“ 一起讨论过后，总结了几个小建议 😆：</p>
<h3>1. 写重试时，务必加上退避与随机抖动</h3>
<p>在封装前端请求、调用第三方 API 或消费消息队列时，不要简单写一个固定次数的死循环重试。</p>
<p>当下游服务真正崩溃时，成千上万个客户端同时无间隔重试，等于在联手攻击自己的后端。务必给重试加上<strong>最大尝试次数</strong>、<strong>递增的等待时间（指数退避）<strong>以及</strong>随机抖动（Jitter）</strong>，错开并发请求，避免形成共振。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/08/4ce3c65795d8aed561f35d20270565b4.webp" alt=""></p>
<h3>2. 监控业务指标，不只看进程存活状态</h3>
<p>很多时候监控看板上的 CPU、内存一切平稳，容器状态也全绿，但核心业务可能已经不可用了。</p>
<p>这次事故中大部分基础服务进程并没有挂，瘫痪的是最前端的鉴权通道。日常开发中，应将<strong>接口真实状态码分布（4xx/5xx）</strong>、<strong>核心业务成功率</strong>与 <strong>耗时</strong>作为第一梯队的告警依据。</p>
<h3>3. 关注调用链条上的每一层代理限制</h3>
<p>在现代架构中，请求到达业务代码之前往往要经过浏览器、Nginx 反向代理、API 网关以及各种辅助服务。</p>
<p>业务代码写得再快，只要前面的 Nginx 连接数超标或辅助服务代理并发被打满，请求在进入代码前就已经失败了。做压力测试和容量规划时，必须将整条网络路径上的各层代理限制一并纳入评估。</p>
<h2>最后</h2>
<p>看来，强如 GitHub 这样的顶级工程师团队依然会犯错。在复杂系统中，很多看似为了“保障可用性”而编写的重试与容错逻辑，在极端场景下极易异化为击垮系统的最后一把推手。</p>
<p>建立重试边界，管控调用放大。下一次在代码中执行重试时，值得多为系统的极限承载力多考虑一些异常情况。</p>
<p>还有一个挺逗的事情，故障发生时，推特上有条关于 500 报错页的高赞调侃：</p>
<blockquote>
<p>GitHub 不应该用独角兽作为服务不可用页面的 UI 图片，因为独角兽几乎看不到，而 GitHub 经常挂掉。</p>
</blockquote>
<p>调侃归调侃，其实每次 GitHub 出故障，都是一次近距离学习大型服务故障防护的好机会 😆。</p>
<p>好了，今天就到这里，大家下次见~</p>
<hr>
<p><img src="https://de4965e.webp.li/blog-images/2026/08/7f8c7e2f6dcdc79b937a398de4ab0e9c.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[opencodex 实测：Codex 与 Claude Code 的账户池与任意模型路由方案]]></title>
            <link>https://i5lin.top/blog/post/5148759953</link>
            <guid isPermaLink="false">5148759953</guid>
            <pubDate>Fri, 14 Aug 2026 05:53:05 GMT</pubDate>
            <description><![CDATA[在 Codex 里使用 DeepSeek？没问题！Opencodex 账户池自动接力，多家供应商轻松聚合，模型随便路由，日常使用几乎无感。]]></description>
            <content:encoded><![CDATA[<p>hello！大家好！</p>
<p>我从 Codex 发布初期就开始使用了。当时由于预算有限，我既无力承担 20x 的高阶套餐，甚至连 5x 套餐也难以负担，只能勉强维持一个 Plus 订阅。</p>
<p>随着需求增长，单个 Plus 账号已无法满足我的使用量，于是我开始转向第三方中转服务。然而，随着 OpenAI 对低价账号打击力度的加大，中转服务的价格一路飙升。最终，我还是回归了官方渠道，并同时订阅了多个 Plus 账号以满足使用需求。</p>
<p>重度使用 Codex Plus 时，每周的额度往往很快耗尽。为了缓解这一问题，我曾尝试使用各类 CLI 工具及知名的 CC Switch 来辅助切换账号，并借此度过了一段过渡期。</p>
<p>然而，频繁地打开终端或通过 CC Switch 切换账号，再重启 Codex 的过程很快便令我感到厌倦。于是，我开始寻求一种更高效的解决方案。</p>
<h2>我想要的其实是一个「账户池」</h2>
<p>在寻找解决方案之前，我把我的需求捋了一遍：</p>
<ul>
<li>几个账号一起认证登录，不要手动登出登入；</li>
<li>新会话自动挑余量最多的账号，实现负载均衡；</li>
<li>已在跑的会话不要动，长任务中途换账号效果不佳；</li>
<li>每个账号的用量状态能一眼看到，不需要逐个登录去查，满足一下个人查看额度的需求。</li>
</ul>
<p>如上所述，我真正需要的是在“个人”与“官方服务”之间建立一层管理机制，而目前的官方客户端并不提供此项功能。</p>
<p>前几天刷 GitHub，正好刷到一个对上这个需求的项目：<a href="https://github.com/lidge-jun/opencodex">lidge-jun/opencodex</a>。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/08/57bb3ab252201439f6b2fbaade779d4d.png" alt="opencodex 架构图：左侧 Codex 客户端通过 /v1/responses 把请求发给本地代理，右侧代理对接各家 provider"></p>
<p>这是一个本地驻留代理，所有 Codex 请求均经由它进行中转，并根据逻辑自动分发至指定的账号与模型。</p>
<h2>账户池的两条关键规则</h2>
<p>装好之后，再逐个添加账号。它有两个行为，正好对上我的需求。</p>
<p>现有会话将固定在原账户上。会话会绑定至启动它的账户，并在后续轮次中持续复用。即使在 SSH 或 tmux 等长会话场景下，也不会出现中途被切换账户的情况。</p>
<p>新会话将自动路由至负载最低的账户。系统会对比 5 小时、每周这三两时间窗口的用量，并选取压力最大的指标作为参考。当活跃账户达到阈值后，新会话将自动分配给余量充足的合格账户。</p>
<p>这两条规则相辅相成，有效解决了核心痛点：在高峰期，系统能够将压力均衡分配至多个账户；同时，对于正在进行的会话，又确保了其持久性，避免了中途切换带来的干扰。</p>
<p>仪表盘还能一键刷新所有账户配额，三个窗口的用量一屏看完，不用再逐个登录。</p>
<p>正好我手头还有 DeepSeek 和 SuperGrok，也一起添加进来！</p>
<p>在仪表盘中开启 Codex Auth 后，即可添加池账户，并可查看下一会话所分配的账户。若关闭自动切换功能，系统将始终锁定并使用当前账户。</p>
<p>另一个体现功底的细节是：当凭证失效时，系统会将账户标记为“需重新认证”，而非静默替换；当收到 429 配额限制响应时，账户会进入冷却状态，后续任务将自动切换至资源池中的其他可用账户。</p>
<p>该报错时即报错，因为代理后台擅自替换凭证反而会带来不可控的风险。</p>
<h2>毫无存在感</h2>
<p>其他工具很多是「有存在感」的：要你记得打开，或者记得反复切换。</p>
<p>而 Opencodex 其最大的优势在于：使用体验极其自然，几乎察觉不到它的存在。</p>
<p>模型选择器依然沿用 Codex 原生逻辑，命令调用与日常操作保持一致，会话及历史记录位置也未作变动。当某个账号达到使用上限时，系统会自动平滑切换至下一个可用池；若非仪表盘中留有请求日志，我甚至察觉不到系统已完成了接力。</p>
<p>我的直观感受是：将多个账号的配额整合在一起，体验如同拥有了一个无限额度的账号。这正是我所追求的理想状态——让工具隐于后台，使我能专注于任务本身。</p>
<p>想看后台在发生什么，打开 opencodex 的仪表盘：provider、账户配额、实时请求日志都非常完整，如果需要可以随时查看。</p>
<h2>模型自助，随你喜欢</h2>
<p>切换账号仅仅是我的入门需求；OpenCodeX 的核心优势在于它彻底开放了模型选择权。</p>
<p>它支持同时接入多家供应商，包括 Anthropic、Google、xAI、DeepSeek、GLM、Kimi、OpenRouter，以及本地的 Ollama。任何兼容 OpenAI 标准的端点均可接入，README 中列出的内置提供商已超过 40 家。配置完成后，所有模型将统一呈现在同一个选择器中。</p>
<p>我目前同时接入了 DeepSeek 和 OpenCode Go 套餐路由的一系列模型。使用时，只需在选择器中按需切换，体验与原生模型无异，甚至还能针对特定模型调节推理强度（reasoning effort）。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/08/a00c73e9e54d83ae7371dc8d2ff65ed3.png" alt="Codex App 模型选择器里出现 opencodex 路由模型，包括 opencode-go 路由的 DeepSeek、GLM、Kimi 等，还能选 Reasoning effort"></p>
<p>命令行里也可以用 -m 直接指定：</p>
<pre><code class="language-bash">codex -m &quot;anthropic/claude-opus-5&quot; &quot;解释这个 stack trace&quot;

codex -m &quot;ollama/llama3&quot; &quot;重构这个函数&quot;
</code></pre>
<p>当省略 <code>provider/</code> 前缀时，系统会自动路由至默认提供商，或根据模型名称模式进行匹配（例如，<code>claude-</code> 开头的模型将路由至 Anthropic，<code>gpt-</code> 开头的模型将路由至 OpenAI）。</p>
<p>Claude Code 也具备同样的功能。使用 <code>ocx claude</code> 启动时，其选择器逻辑与原生保持一致，用户可根据需求自由选择模型。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/08/91da99e06ffb006dc74ddad2cea7fd0e.gif" alt="Claude Code 通过 opencodex 跑路由模型，状态栏显示当前模型为 gpt-5.6-luna-medium"></p>
<p>启用此服务后，您可以在各类 CLI 工具的模型切换列表中看到并使用您添加的所有模型，随心所欲。即使在 Claude Code 中使用 DeepSeek，体验也如同官方原生支持一般，完美契合您的使用习惯。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/08/d23398c70e5f57888cae69c3e13963d3.gif" alt="Codex 应用里用路由的非 OpenAI 模型执行任务"></p>
<p>认证方式多种多样：xAI、Anthropic 及 Kimi 支持 OAuth 登录，可实现 Token 自动刷新；此外，也支持通过转发 Codex 登录、直接粘贴 API Key 或引用环境变量（${ENV_VAR}）进行配置。</p>
<h2>它还是本机网关：其他软件也能接进来</h2>
<p>在使用一段时间后，我发现了它的另一重身份：整台机器的本地网关。</p>
<p>OpenCodex 在本地 127.0.0.1:10100 提供统一的 API 入口。任何支持自定义请求地址的软件——无论是编辑器插件、自研脚本还是第三方客户端——均可将请求交由其处理，从而实现对同一供应商及账户池的复用。</p>
<p>这意味着本地工具无需再分别配置 API Key 或路由规则；只需在网关完成一次配置，即可实现全局共享。对于喜欢折腾工具的用户而言，这一设计极为实用。</p>
<p>以 AI 翻译软件为例，若需接入 OpenAI 服务，通常需要配置专属 API Key。此时，用户可以直接使用 OpenCodex 网关替代官方服务。鉴于目前许多软件已支持自定义 AI 服务商，这种应用方式具有非常广泛的适用性。</p>
<h2>不是唯一方案，只是我个人喜欢</h2>
<p>先说清楚，这类需求的方案不止一个。</p>
<p>再次介绍 <a href="https://github.com/farion1231/cc-switch">CC Switch</a>，一个开源跨平台桌面应用，统一管理 Claude Code、Codex、Gemini CLI 等工具的供应商配置，一键切换 provider，还带 MCP 和 Skills 管理。</p>
<p>OpenCodex 采取了截然不同的路径：通过常驻代理、账户自动接力，以及将模型直接集成至原生选择器，一旦配置完成，几乎可以实现无感使用。我个人更倾向于后者——我希望将模型切换的工作完全交给工具处理，从而彻底摆脱对此类琐事的关注。</p>
<p>类似思路的工具还有好几个，萝卜青菜各有所爱，选择自己喜欢的就好！</p>
<h2>安装和几个实用命令</h2>
<p>安装就两条命令，需要 Node 18+：</p>
<pre><code class="language-bash">npm install -g @bitkyc08/opencodex
ocx start
</code></pre>
<p>ocx start 会启动代理，并打开 <a href="http://localhost:10100">http://localhost:10100</a> 的仪表盘；添加 provider、选模型、管账户都在网页里完成。随时可以跑 ocx gui 重新打开。想后台常驻、开机自启，用 ocx service。</p>
<p>几个常用命令：</p>
<ul>
<li><code>ocx status</code>：看代理是否在运行</li>
<li><code>ocx account list</code>：查看账户和用量，脱敏显示</li>
<li><code>ocx stop</code>：停掉代理，并把 Codex 恢复成原始配置</li>
</ul>
<p>最后一点尤为值得一提：在停止运行 OCX 后，Codex 的表现如同从未安装过 OpenCodex 一般——既无残留配置，也无僵尸进程。对于这类会修改客户端配置的工具而言，能够实现“干净退出”至关重要。</p>
<p>没有人希望软件卸载后仍留有冗余文件，正如没有人喜欢 macOS 在用户目录中留下 .DS_Store 文件一样 😆。</p>
<blockquote>
<p>安装时如果遇到 bundled Bun runtime is missing，说明 npm 拦截了安装脚本。用 npm install -g --allow-scripts=bun @bitkyc08/opencodex 重装，别加 --ignore-scripts。</p>
</blockquote>
<h2>用之前要知道的几件事</h2>
<p>若您打算尝试使用 OpenCodex，请先注意以下几点：</p>
<p>OpenCodex 是一个社区驱动的开源项目，与 OpenAI、Anthropic 等官方机构无任何关联。请注意，部分服务提供商（尤其是 Anthropic）可能会对通过第三方代理路由 API 流量的账户采取限制措施。正如 README 文档中所明确声明的：使用风险需由用户自行承担。</p>
<h2>最后</h2>
<p>回顾这一需求，初衷其实很简单：摆脱繁琐的账号切换，无需担忧配额耗尽，并能根据需求自由选择模型。</p>
<p>OpenCodex 将这三个痛点整合为一个常驻本地代理：通过自动轮询账号池和聚合多供应商接口，使其成为整台机器的统一网关。如果你也厌倦了在多个账号间频繁切换，花十分钟尝试一下非常值得。</p>
<p>AI 的发展实在太快，也许这个方案再过一段时间就过时，到时候如果有更好的方案，我还会分享出来给大家！</p>
<hr>
<p><img src="https://de4965e.webp.li/blog-images/2026-07/95ad239eab6a898699ec13740ddebb81.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.80]]></title>
            <link>https://i5lin.top/blog/post/5039241296</link>
            <guid isPermaLink="false">5039241296</guid>
            <pubDate>Sat, 01 Aug 2026 22:03:47 GMT</pubDate>
            <description><![CDATA[本期分享额度查看工具、Agent 学习资料、TanStack Intent、Tailwind class 合并、Grok Build 源码解读、WebGL 动效、图片渲染组件、Cloudflare Agent Skills 和 Otty 终端。]]></description>
            <content:encoded><![CDATA[<p>各位读者，大家好。</p>
<p>开发过程中，及时看到状态、找到资料，再配上一点有趣的交互，往往能省掉不少重复操作。这期继续分享几个可以直接留在工作流里的项目。</p>
<p>本期主要内容：</p>
<ul>
<li>CodexBar —— 在 macOS 菜单栏查看 AI 服务额度和重置时间</li>
<li>Pane —— 在 Windows 托盘查看多个 AI 订阅的使用情况</li>
<li>ai-agent-book —— 用开源书籍和配套代码学习 Agent</li>
<li>grok-build —— 从源码拆解终端 AI Coding Agent</li>
<li>TanStack Intent —— 让 Agent Skills 随 npm 包一起提供</li>
<li>cnfast —— 更快的 Tailwind class 合并工具</li>
<li>Sticker Forge —— 用 WebGL 制作可以撕开的互动贴纸</li>
<li>Canvas UI —— 可复制到项目里的 Canvas 和 WebGL 组件库</li>
<li>Takumi —— 将 JSX 和 HTML 渲染成图片、SVG 与动画</li>
<li>Kinetics —— 基于弹簧物理的网页交互动效库</li>
<li>Cloudflare Skills —— 为 Agent 提供 Cloudflare 平台开发知识</li>
<li>Otty —— Typora 团队推出的原生终端应用</li>
</ul>
<h3>CodexBar</h3>
<p><a href="https://github.com/steipete/CodexBar">CodexBar</a> 是一个 macOS 菜单栏应用，支持查看 Codex、OpenAI、Claude、Cursor 等服务的 session、weekly、monthly 窗口和 reset 倒计时。README 当前列出 63 个 provider，准备开始长任务前，可以先看一眼还有多少额度。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/08/c40cdc071c1ff4b5fbad2c0b3cbe4856.png" alt="CodexBar 菜单栏预览"></p>
<p>它还支持服务状态、credits 和部分花费统计。CodexBar 会复用已有的 OAuth、API key、browser cookie 或本地文件；启用浏览器 cookie 时，macOS 可能会询问 Keychain 或 Full Disk Access，使用前按需打开 provider 即可。</p>
<h3>Pane</h3>
<p><a href="https://github.com/ItsJazii/pane">Pane</a> 是一个 Windows 托盘应用，用来查看 Claude、Codex、Cursor、Copilot 等服务的额度、重置时间和本地花费。它会读取本机已有的 CLI 或编辑器登录状态，找到对应工具后再显示卡片。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/08/7be3c720fd898e946e16489c64ac09a0.png" alt="Pane 托盘应用预览"></p>
<p>Pane 还会根据消耗速度推算能否撑到下一次 reset，并在额度接近用完时发送 Windows 通知。它分析本地日志并结合模型价格估算花费，没有公开价格的模型只保留 token 数量，重要账单仍然以服务商后台为准。</p>
<h3>ai-agent-book</h3>
<p><a href="https://github.com/bojieli/ai-agent-book">《深入理解 AI Agent：设计原理与工程实践》</a> 是李博杰开源的 Agent 技术书，包含中文正文、编译好的 PDF 和按章节组织的配套代码。</p>
<p>全书共十章，从 Agent 基础、上下文工程、记忆、MCP 和工具设计，一直写到 Coding Agent、评估、多模态和多 Agent 协作。书中用“Agent = LLM + 上下文 + 工具”概括 Agent 的组成，也提供了 agent-skills-ppt 等可以继续动手的示例，适合边读边跑。</p>
<h3>grok-build</h3>
<p><a href="https://zhanghandong.github.io/grok-build/">grok-build 在线书</a> 以 xAI 开源的终端 AI Coding Agent Grok Build 为主线，从 Rust 源码拆解 agentic loop、上下文压缩、工具系统、沙箱隔离、终端增量渲染和扩展生态，并对照 Codex 与 OpenCode 的实现。</p>
<p>配套的 <a href="https://github.com/ZhangHanDong/grok-build">ZhangHanDong/grok-build 源码仓库</a>包含 75 个 crate。书中架构结论都带有 file:line 引用，也提供按 Agent 内核、工具与安全、终端 UI、扩展平台划分的阅读路径，适合想理解 Coding Agent 内部结构的人。</p>
<h3>TanStack Intent</h3>
<p><a href="https://tanstack.com/intent/latest/docs/overview">TanStack Intent</a> 是 TanStack 用来发布和消费 Agent Skills 的 CLI 与配套机制。</p>
<p>你在 React Query（现在的 @tanstack/react-query）node_modules 里看到的 Agent 文档，就属于这套机制：Skill 以 SKILL.md 的形式跟随 npm 包发布，Agent 可以从项目依赖中发现并加载与任务匹配的文档。这样，库的使用规范会跟着版本更新，不必一直依赖模型记忆或手工维护一份旧说明。</p>
<h3>cnfast</h3>
<p><a href="https://github.com/aidenybai/cnfast">cnfast</a> 是一个兼容 tailwind-merge 的 cn 工具，可以合并和整理 Tailwind CSS class，并保持与原工具一致的输出结果。它支持从现有的 clsx、classnames 或 tailwind-merge 项目迁移。</p>
<p>项目基准测试显示，cnfast 在多种场景下平均比 tailwind-merge 快 3.8 倍，重复渲染时提升更明显。对于频繁重新渲染的组件、数据表格或实时界面，这类小工具可以减少 class 合并本身带来的开销。</p>
<h3>Sticker Forge</h3>
<p><a href="https://github.com/CatsJuice/sticker-forge">Sticker Forge</a> 是一个基于 WebGL 的互动贴纸生成器，可以输入文字或上传图片，再用鼠标把贴纸撕起来，看到卷曲、阴影和贴纸背面的立体效果。</p>
<p>项目同时提供可直接使用的 Web Component 和 JavaScript API，适合放进产品落地页、作品展示或需要一点互动感的网页里。它的重点不是生成一张静态贴纸，而是把“撕开”这个动作做成了可复用的交互组件。</p>
<h3>Canvas UI</h3>
<p><a href="https://canvasui.dev/">Canvas UI</a> 是一个开源的 Canvas 和 WebGL 组件库，提供粒子、液态、玻璃、碎裂和 VHS 等视觉效果。组件通过 shadcn CLI 复制到项目中，代码留在自己的仓库里，不需要额外安装运行时包。</p>
<p>它支持 React、Solid、Preact、Vue、Svelte 和原生 TypeScript，适合给网页首屏、作品展示或交互页面增加动态效果。对希望直接拿源码调整，而不是引入一套黑盒动画库的人比较方便。</p>
<h3>Takumi</h3>
<p><a href="https://github.com/kane50613/takumi">Takumi</a> 是一个用 Rust 编写的渲染引擎，可以把 JSX、HTML 或节点树转换成 PNG、JPEG、WebP、SVG、GIF、APNG 和视频帧，不需要启动 headless browser。</p>
<p>它兼容 next/og 的 ImageResponse API，支持 Node.js、Cloudflare Workers、浏览器和 Rust，也覆盖 CSS Grid、伪元素、渐变、Tailwind utilities 等常见能力。适合生成 OG 卡片、社交分享图、文档图片或动画素材。</p>
<h3>Kinetics</h3>
<p><a href="https://kinetics.colorion.co/">Kinetics</a> 是一个基于 spring physics 的网页交互动效库，收集了按钮、卡片、Tab、拖拽、提示框、光标轨迹等 117 个可直接参考的效果。</p>
<p>每个效果都提供 CSS、React 和可复制的 AI prompt，并显示 stiffness、damping 等参数。想给页面加一点有重量感的反馈，可以先从这里挑一个效果，再把代码和参数复制到自己的项目里。</p>
<h3>Cloudflare Skills</h3>
<p><a href="https://github.com/cloudflare/skills">Cloudflare Skills</a> 是 Cloudflare 提供的一组 Agent Skills，覆盖 Workers、Agents SDK、Durable Objects、Sandbox SDK、Wrangler、Web Performance 和 MCP Server 等开发场景。</p>
<p>如果经常让 Agent 编写 Cloudflare 项目，可以把它当成一套现成的上下文资料，让 Agent 更准确地处理平台 API、部署方式和常见架构。项目兼容支持 Agent Skills 标准的工具，包括 Claude Code、OpenCode、Codex 和 Pi。</p>
<h3>Otty</h3>
<p><a href="https://otty.sh/">Otty</a> 是 Typora 背后的 appmakes 团队推出的原生终端应用，目前提供 macOS Apple Silicon 和 Intel 版本，Windows、Linux、iOS 版本可以加入等候名单。</p>
<p>它支持 tabs、splits、session recovery、命令面板、inline autocomplete 和长任务通知，也为并行运行多个 Claude Code、Codex 等 Agent 提供任务监控、会话历史、Prompt Queue 和输出回传。适合想保留终端的键盘操作，又需要管理多个 Agent 会话的人。</p>
<h2>最后</h2>
<p>细节的打磨是优秀产品与普通产品的重要分界线。希望本期分享的内容能为你带来一些新的思考和启发。</p>
<p>感谢阅读本期周刊，我们下期再见。</p>
<hr>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/95ad239eab6a898699ec13740ddebb81.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[在公众号里贴代码？我觉得把代码变成图片阅读体验更好]]></title>
            <link>https://i5lin.top/blog/post/5015617844</link>
            <guid isPermaLink="false">5015617844</guid>
            <pubDate>Thu, 30 Jul 2026 02:59:10 GMT</pubDate>
            <description><![CDATA[从移动端公众号阅读体验出发，聊聊为什么把代码转成图片更适合文章传播，并介绍 Codia 的功能、用法，以及如何接入公众号写作、Agent 和自动化发布工作流。]]></description>
            <content:encoded><![CDATA[<p>hello！大家好！</p>
<p>最近我在写公众号文章的时候，又被代码块折磨了一次。</p>
<p>事情是这样的，文章里只是想放一段配置示例，让读者知道某个 API 怎么调用。电脑上预览还好，代码块有等宽字体，有缩进，也能横向滚动。可是一到手机上，体验立刻变得很差。</p>
<p>读者要么看到一行代码被压得很窄，换行以后缩进全乱；要么需要左右滑动，一不小心又把整篇文章带着动。</p>
<p>我自己作为作者也很痛苦。写文章的时候，代码不是为了让读者复制粘贴一大坨。</p>
<p>很多时候，它只是一个视觉证据：这里确实有一个参数、一个调用方式、一个配置片段、一个关键 diff。</p>
<p>读者需要的是一眼看懂结构，而不是在手机上和横向滚动条搏斗。</p>
<p>所以我最近试着做了一件事：<strong>把文章里的关键代码转成图片。</strong></p>
<p>不是所有代码都转。需要复制的完整代码，还是应该放链接、仓库、Gist 或文档页。但在公众号正文里，那些用来解释、展示的代码片段，图片反而更适合阅读。</p>
<p><strong>读者在阅读的时候，通常不会需要复制代码。</strong></p>
<h2>代码图片解决的不是“好看”</h2>
<p>很多人第一次听到“代码转图片”，会以为它只是让代码变漂亮。</p>
<p>当然，漂亮是有用的。公众号文章本来就是移动端阅读场景，图文排版、留白、色彩、层级都会影响读者愿不愿意继续往下看。</p>
<p>但我真正关心的不是装饰感，而是阅读稳定性。</p>
<p>代码变成图片以后，移动端至少有几个变化：</p>
<ul>
<li>缩进不会被微信编辑器重新解释；</li>
<li>长行不会在奇怪的位置硬折断；</li>
<li>主题、行号、语言标识可以保持一致；</li>
<li>读者不需要左右滑动代码块；</li>
<li>作者可以控制图片宽度、留白和背景，让它和正文节奏更合拍。</li>
</ul>
<p>这对技术文章尤其明显。</p>
<p>比如我写一段 JSON 配置，如果直接贴在公众号里，手机上一换行，层级感就没了。读者原本只需要看 model、tools、messages 之间的关系，结果注意力全被滚动操作占掉。</p>
<p>如果把它转成一张清晰的图片，读者就能像看截图一样扫过去：先看整体结构，再看重点字段。它虽然适合复制，但非常适合理解。</p>
<h2>怎么取舍？</h2>
<p><strong>正文讲解用图片，完整可复制代码放链接。</strong></p>
<p>也就是说，如果这段代码承担的是“解释”和“展示”的任务，我会把它转成图片；</p>
<p>如果它承担的是“交付”和“复用”的任务，我会给 GitHub、文档页、npm 包、完整示例仓库，或者在文末补一个可复制版本。</p>
<p>比如这些场景很适合代码图片：</p>
<ul>
<li>展示一个 API 请求长什么样；</li>
<li>展示某个配置文件的关键字段；</li>
<li>展示重构前后的差异；</li>
<li>展示 CSS、SQL、正则这种需要语法高亮的片段；</li>
<li>展示 Agent 生成的结果摘要；</li>
<li>展示“这一段就是问题所在”的代码证据。</li>
</ul>
<p>这些场景则不适合只放图片：</p>
<ul>
<li>教程里要求读者照着运行的完整代码；</li>
<li>很长的源码文件；</li>
<li>需要复制的命令；</li>
<li>读者可能需要搜索、改写、粘贴到 IDE 的内容。</li>
</ul>
<p>所以代码图片不是替代代码文本，而是增强读者阅读体验的可选方案。</p>
<h2>这就是我做 Codia 的原因</h2>
<p>Codia 是我做的一个小工具，目标很直接：<strong>把源代码生成适合人阅读、也适合 API 调用的代码图片。</strong></p>
<p>它不是一个只给网页点点点用的小玩具，也不是只能在本地跑的截图脚本。它同时提供了几种入口：</p>
<ul>
<li>Playground：在浏览器里粘贴代码，调语言、主题、图片格式、背景、留白、宽度、行号，然后复制或下载图片；</li>
<li><code>POST /v1/code/render</code> API：用 JSON 请求生成代码图片，适合脚本、服务端、自动化流程；</li>
<li><code>/docs</code> 和 <code>/api/docs</code>：给人看的 API 文档和 OpenAPI 文档；</li>
<li><code>/llms.txt</code>：给 Agent 看的说明入口；</li>
<li>MCP：让支持 MCP 的 Agent 直接调用 <code>render_code_image</code> 或 <code>render_code_image_advanced</code>。</li>
</ul>
<p>如果你只是偶尔写文章，最简单的方式就是打开 Playground。</p>
<p>把代码粘进去，系统会自动识别语言，比如 typescript、javascript、python、go、json、css；</p>
<p>再选主题、格式、宽度、行号和背景。调到适合公众号正文的宽度后，直接下载图片，插到文章里就行。</p>
<p>如果你已经有自己的写作流程，Codia 更有意义的地方在 API 和 MCP。</p>
<h2>手动用法：打开 Playground</h2>
<p>最简单的工作流是这样：</p>
<ol>
<li>写文章时先把代码片段留在 Markdown 里；</li>
<li>发布前挑出真正需要展示的代码；</li>
<li>打开 Codia 的 playground 页面；</li>
<li>粘贴代码，选择语言和主题；</li>
<li>调整各种生成图片的参数，使其满足自己的喜好</li>
<li>复制或者下载图片，放回公众号文章。</li>
</ol>
<p>公众号里我一般会更关注三个参数。</p>
<h2>API 用法：把生成图片变成固定步骤</h2>
<p>如果你经常写技术文章，就不应该每次都手动调。</p>
<p>在使用 API 之前，只需要用户登录一下 Chrome 账户，再生成自己的凭证放在请求头里即可！</p>
<p>Codia 提供 POST /v1/code/render，可以从 JSON 生成图片。一个最小请求大概是这样：</p>
<pre><code class="language-bash">curl -X POST http://localhost:4300/v1/code/render \
  -H &quot;content-type: application/json&quot; \
  -d &#39;{
    &quot;language&quot;: &quot;typescript&quot;,
    &quot;theme&quot;: &quot;dracula&quot;,
    &quot;format&quot;: &quot;png&quot;,
    &quot;code&quot;: &quot;console.log(\&quot;hello codia\&quot;)&quot;,
    &quot;containerWidth&quot;: 600,
    &quot;borderSize&quot;: 12,
    &quot;showLineNumbers&quot;: true
  }&#39;
</code></pre>
<p>默认返回 JSON，里面有 dataUrl、imageBase64、图片元数据和 recordId。如果设置 responseType: &quot;image&quot;，可以直接拿到图片二进制。Pro/Admin 用户还可以用 responseType: &quot;cdnUrl&quot;，把图片上传到配置好的 R2/S3 兼容存储里，直接拿 CDN URL。</p>
<p>这样一来，代码图片就可以进入工作流，而不是停留在“我打开网页手动做一张图”。</p>
<p>比如你可以写一个小脚本，扫描文章里的代码块，把带有特殊标记的代码块发给 Codia，然后把返回的图片地址插回 Markdown。</p>
<p>我很喜欢这种方式，因为它把审美变成了一个稳定步骤：主题、宽度、留白、行号规则都固定下来。以后每篇文章里的代码图看起来是一套系统，稳定性大大提升。</p>
<h2>Agent 用法：让 AI 写文章时顺手出图</h2>
<p>Codia 还提供 MCP，这个对我自己的写作特别有用。</p>
<p>现在很多文章我会让 Agent 参与：整理资料、改结构、补示例、做发布前检查。以前让 Agent 处理代码图片很麻烦，它得知道 API 怎么调用、图片怎么保存、哪些参数适合文章。</p>
<p>有 MCP 以后，Agent 可以直接调用工具。</p>
<p>Codia 里有两个主要工具：</p>
<ul>
<li><code>render_code_image</code>：适合常见场景，参数比较少，可以选 <code>github</code>、<code>twitter</code>、<code>docs</code>、<code>blog</code>、<code>glass</code>、<code>macos</code>、<code>minimal</code> 这些 preset；</li>
<li><code>render_code_image_advanced</code>：适合精细控制，可以设置完整渲染参数，比如 <code>theme</code>、<code>bgColor</code>、<code>borderSize</code>、<code>containerWidth</code>、<code>showLineNumbers</code> 和 <code>quality</code>。</li>
</ul>
<p>这意味着我可以在写作任务里直接说：</p>
<blockquote>
<p>把文中这三段代码生成适合公众号正文的代码图片，JSON 配置用 docs 风格，TypeScript 示例用 blog 风格，图片宽度控制在 600 左右，保留行号。</p>
</blockquote>
<p>Agent 就不需要再“理解图片生成原理”，它只要调用 Codia 的工具，把结果放进文章资产流程里。</p>
<p>工具越适合 Agent 调用，写作流程就越容易自动化。尤其是公众号文章这种有固定生产节奏的内容：选题、资料、正文、配图、代码图、封面、排版、草稿，每一步都可以慢慢沉淀成 workflow。</p>
<h2>我现在会放进哪些工作流</h2>
<p>第一个是公众号写作。</p>
<p>我会在 Markdown 里保留原始代码，发布前让 Agent 判断哪些代码适合图片化。短配置、关键 diff、核心 API 示例转成图片；需要复制的命令保留文本，并给出仓库或文档链接。</p>
<p>第二个是项目文档。</p>
<p>文档正文里有些代码是为了精读，有些只是为了“长这样”。后者很适合转成图片，尤其是 README、产品介绍页、更新日志、社交平台分享图。Codia 的 API 可以让这些图保持统一风格。</p>
<p>第三个是 AI 工作日志。</p>
<p>比如让 Agent 总结一次重构，它可以把关键代码片段、前后对比、配置变化生成代码图片，再配上文字说明。这样分享给团队或发到文章里，比贴一堆 Markdown code block 更容易被读完。</p>
<p>第四个是发布流水线。</p>
<p>如果你的文章本来就从 Markdown 进入某个发布系统，可以在构建阶段做一件事：识别带标记的代码块，调用 Codia 生成图片，上传到图床或 CDN，再把图片 URL 写回最终稿。</p>
<p>像这样给代码块加一个约定：</p>
<pre><code class="language-markdown">
```ts
const message = &quot;hello codia&quot;
console.log(message)
```
</code></pre>
<p>真正发布前，脚本或 Agent 把它变成一张图。原始代码仍然留在仓库里，公众号看到的是图片。</p>
<p>这个思路的好处是，作者不用在写作时打断心流。写的时候还是写 Markdown，发布的时候再统一处理。</p>
<h2>不要滥用</h2>
<p>代码图片也有代价。</p>
<p>它不能被读者直接复制，搜索引擎也不容易理解图片里的代码，屏幕阅读器体验也会下降。如果一篇教程全是代码图片，那对真正想跟着做的读者并不友好。</p>
<p>所以建议它当成“展示层”，不是“源码层”。</p>
<p>比较理想的做法是：</p>
<ul>
<li>正文里放代码图片，保证移动端阅读顺滑；</li>
<li>文末放完整仓库、文档或可复制代码；</li>
<li>图片 alt 或上下文说明里写清楚这段代码展示的是什么；</li>
<li>长代码拆短，只展示关键部分；</li>
<li>不把图片当成唯一的信息来源。</li>
</ul>
<p>技术内容最终还是要对读者负责。好看的代码图可以降低阅读门槛，但不能牺牲可访问性和可复现性。</p>
<h2>最后</h2>
<p>公众号是一个很典型的移动端阅读场景，而代码天生更适合横向空间。</p>
<p>该复制的地方，给文本和链接；该阅读的地方，给一张稳定、清晰、有层次的图片。作者少一点排版焦虑，读者少一点手机上左右滑动的烦躁。</p>
<p>Codia 目前就是围绕这个需求做的：给人一个 Playground，给程序一个 API，给 Agent 一个 MCP。你可以手动用，也可以把它接进自己的写作、文档和发布 workflow 里。</p>
<p>Codia 也是我第一个比较完整的个人作品，目前任然处于早期开发阶段，欢迎大家试用看看！</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[从 Redis 作者到 Uncle Bob：人工智能时代还该不该读代码？]]></title>
            <link>https://i5lin.top/blog/post/4966333124</link>
            <guid isPermaLink="false">4966333124</guid>
            <pubDate>Fri, 24 Jul 2026 07:38:34 GMT</pubDate>
            <description><![CDATA[Redis 创作者 antirez 认为，逐行阅读人工智能生成的代码正在变成开发者的瓶颈。这个观点很刺激，但它适用于所有项目吗？]]></description>
            <content:encoded><![CDATA[<p>大家好！</p>
<p>如果 AI 一天生成 5000 行代码，你会怎么做？</p>
<p>坐下来，从第一个函数开始读，读到最后一个括号？还是先看整体的设计、跑测试、观察程序的运行行为，只有遇到可疑的地方才打开具体实现？</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/4086ddf0952c40a8012c5aee042ad172.webp" alt="代码洪水与设计"></p>
<p>Redis 创作者 antirez 最近给出了一个相当激进的答案：很多开发者没有真正发挥自动编程的能力，原因就在于他们还在看代码。</p>
<h2>“看代码”正在变成瓶颈？</h2>
<p>2026 年 7 月 12 日，antirez 发了一条很容易引发争论的推文：</p>
<blockquote>
<p>It is my belief that many devs right now are not maximizing what they can do with automatic programming because they still look at the code. Doing it makes you the bottleneck. Your time is better invested in new ideas, QA, design, and asking yourself what is your goal.</p>
</blockquote>
<p>大意是：“亲自做这件事，会让你成为瓶颈。你的时间更应该投入到新想法、质量验证和设计上。”</p>
<p>这条推文的<a href="https://nickbell.dev/shares/antirez-at-antirez-on-x">文字记录</a>显示，它获得了超过 2500 个点赞，超过 149 万阅读量。<strong>antirez</strong> 的意思很明确：如果 AI 已经可以持续生成代码，开发者还把大量时间花在逐行阅读上，自己的时间就会被代码生产速度拖住。</p>
<p>这个观点听起来像是在说， AI 写代码超快，程序员反而是拖慢整体速度里，木桶最短的那一块。</p>
<p>我们花了这么多年学习语法、数据结构、并发、内存和各种框架，最后却有人提出这样的建议：代码最好少看一点？</p>
<h2>他真正反对的，可能是“默认逐行审查”</h2>
<p><strong>antirez</strong> 后来在博客里写了<a href="https://antirez.com/news/169">《控制思想，而不是代码》</a>，把这件事完整解释了一遍，有兴趣的读者朋友可以去看看。</p>
<p>首先，他并没有建议大家随便写一句提示词，然后等着 AI 交付一个软件。相反，他认为大语言模型 在“局部实现”上已经很强，真正容易出问题的地方往往在更大的设计上：</p>
<ul>
<li>数据结构是否选对；</li>
<li>模块之间的关系是否合理；</li>
<li>性能目标是否清楚；</li>
<li>边界条件有没有被设计进去等等；</li>
</ul>
<p>他认为如果这些事情没有想清楚，开发者就算把生成的每一行代码都读完，也可能只是认真检查了一条错误的路线。</p>
<p>打个比方，这就像拿着放大镜检查一座桥的每颗螺丝，却没有先问桥要承受多少重量、建在哪里、遇到洪水怎么办。螺丝当然要检查，可它们不是整座桥的设计。</p>
<p>antirez 认为，开发者更应该维护一份清晰的设计文档（DESIGN.md），用人类语言写明每个数据结构的设计、实现技巧和关键取舍。以后有人要修改有序集合，不必先在几千行代码里游泳，而是先读懂设计，再让智能代理按照这个心智模型工作。</p>
<p>这里的重点不在于“代码不重要”。重点在于：代码应该从主要阅读对象，变成验证设计是否落地的一种证据。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/37817c5698db2e75b2521ceb7633ef28.webp" alt="设计与代码证据"></p>
<h2>antirez 自己也没有真的完全不看</h2>
<p>他在博客里承认，自己仍然会检查人工智能生成的 Redis 代码。他参与了 Redis 数组功能的实现，也在做 序集合的内存优化，提到后者可能带来 50% 的内存节省。</p>
<p>他会修改自己不喜欢的写法，也会寻找细小的设计问题。</p>
<p>那他为什么还要说逐行审查“<strong>基本没用了</strong>”？</p>
<p>他的解释是：Redis 是很多人依赖的公共软件，用户会直接打开源码、修改源码，所以他继续检查代码，<strong>更多是出于对用户负责</strong>。如果只考虑自己的效率，他宁愿把审查时间拿去做质量保证、想下一步优化，或者补充设计文档（DESIGN.md）。</p>
<p>也就是说，antirez 的观点并不是“代码永远不用看”，而是“逐行看代码不应该再成为所有项目的默认动作”。</p>
<p>对于一个个人工具、一次性的脚本、风险很低的内部功能，逐行阅读可能确实是在浪费时间。对于 Redis 这种基础设施软件，内存、并发、兼容性和错误处理都可能影响大量用户，自然会更谨慎。</p>
<h2>Redis 数组功能的开发过程，也没有那么“放手”</h2>
<p>antirez 介绍 Redis 数组功能 开发过程的<a href="https://antirez.com/news/164">另一篇文章</a>里，反而能看到大量人工参与。</p>
<p>他先花了一个月写规格文档，明确数据结构、稀疏表示、游标语义和操作方式。之后才和 大语言模型 来回讨论设计，再进入实现阶段。代码完成后，他持续检查、重写模块、做压力测试，确认实现足够可靠。</p>
<p>这和“完全不读代码”之间，隔着很长一段距离。</p>
<p>也许更准确的说法是：人要把时间从“平均分配给每一行代码”，改成“集中在最值得怀疑的地方”。</p>
<p>如果一个模块处理内存分配、锁、权限、支付金额或用户数据，那些关键路径当然值得读。可是一个已经被类型系统、单元测试、集成测试和压力测试反复验证的局部实现，是否还值得人工从头扫到尾？</p>
<p>这个比例该怎么分配，没有一个统一答案。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/b2025360717c9ae5f60d5f41411bb936.webp" alt="聚光灯看高风险"></p>
<h2>代码质量，真的只取决于有没有人工看过吗？</h2>
<p>很多人反对 antirez，而且反对的人还不少，很多人担心的是软件质量。</p>
<p>这个担心很合理。AI 可以生成一段看起来完整的代码，也可以顺手写一组看起来完整的测试。问题是，测试本身可能漏掉了关键场景，代码和测试还可能共同建立在一个错误的理解上。</p>
<p>但是，“我看过每一行”也不等于“我理解了整个系统”。</p>
<p>反过来，完全不看代码也有问题。你怎么知道设计文档（DESIGN.md）中的设计真的被实现了？如果文档漏掉了一个锁的顺序、一个整数溢出条件，或者一个异常路径，谁来发现？</p>
<p>代码有时候像建筑的施工现场。设计图能告诉你房子想怎么建，但墙里有没有少几根钢筋，总不能永远靠设计图推断吧？</p>
<p>所以，真正影响 AI 代码质量的因素，可能至少包括：</p>
<ul>
<li>模型本身的能力；</li>
<li>任务范围是否清晰；</li>
<li>代码库是否容易理解；</li>
<li>设计文档有没有写到关键取舍；</li>
<li>智能代理 有没有足够的工具和上下文；</li>
<li>测试、质量保证、监控和回滚是否可靠；</li>
<li>这段代码出错后，会不会带来严重后果等等。</li>
</ul>
<p>在一个干净的小项目里，AI 可能给你一个很好的结果。在一个历史包袱很重、边界条件很多的系统里，同样的模型可能会把问题越改越深，把一切搞砸。</p>
<p>把两种情况用一句“以后不要看代码”按下，显然有点扯淡。</p>
<h2>Uncle Bob：用约束代替逐行阅读</h2>
<p>这也让我想到 <a href="https://x.com/unclebobmartin">Uncle Bob Martin</a> 最近表达的另一个真实策略。他的做法比“少看代码”更激进：他甚至不读 智能代理 写出的代码，而是把 智能代理 放进一整套极端约束里。</p>
<p>他说:</p>
<p>中文翻译：我六十年代末就开始写代码了。我现在的策略是不阅读智能代理写出的任何代码。只有这样，我才能真正利用它们的生产力。我的做法，是给智能代理套上极其严格的约束：单元测试、场景测试、质量保证流程、质量指标、变异测试、测试覆盖率，以及许多其他检查。最终，我对它们生成的代码有很高的信心，因为这些代码必须闯过我设置的所有约束和测试。</p>
<p>他的答案和 antirez 的判断有一个重要差别：antirez 更强调人应该控制设计、心智模型和关键判断；Uncle Bob 则进一步把“信任”外包给一套必须通过的测试与质量门槛。代码不需要被人平均阅读，但必须在单元测试、场景测试、质量保证流程、质量指标、变异测试和测试覆盖率等约束下闯过一轮又一轮检查。</p>
<p>这补上了“不要逐行看代码”最容易被忽略的另一半：不读代码不等于不控制代码。人的控制权从每一行实现，移动到了约束条件、测试场景和验收标准。</p>
<p>当然，约束本身也不是魔法。如果测试漏掉了关键场景，或者场景描述写错了产品意图，智能代理 依然可能很高效地通过一套错误的考试。所以，开发者仍然要负责设计这些约束，确认它们测的是正确的问题，而不是只看最后的结果。</p>
<h2>社区争论，其实很有代表性</h2>
<p>这篇文章在 <a href="https://news.ycombinator.com/item?id=48891184">技术社区的讨论</a>里很快分成了几种声音。</p>
<p>有人认同 antirez 的判断，认为开发者真正应该花时间做架构、写规格、设计测试和思考产品方向。代码已经多到读不完，继续坚持逐行检查，只是在维护一种旧的职业习惯。</p>
<p>也有人提出反问：</p>
<p>如果一个人没有通过写代码、读代码和处理 缺陷 建立心智模型，他凭什么只控制“想法”？设计文档（DESIGN.md）也可能写得很漂亮，却遗漏了真实实现中的细节。更年轻的程序员尤其需要通过实际编码，慢慢获得判断设计好坏的经验。</p>
<p>还有一种意见我觉得很值得注意：代码审查本来就不应该等于审查所有代码。很多开发者会选择重点查看公共 接口、关键数据结构、并发逻辑和高风险路径，把其他部分交给测试与自动化工具。</p>
<p>这可能更接近大多数团队现在能落地的方式。</p>
<h2>从代码工匠变成系统的掌舵者？</h2>
<p>antirez 想推动的，也许是一种工作身份的变化。</p>
<p>过去，程序员像工匠一样亲自加工每个零件。代码是一行一行写出来的，理解系统也常常从阅读这些代码开始。</p>
<p>现在，AI 可以加工大量零件，而且速度极快。打个比方，程序员更像航海船长：决定船往哪里开，规定不能驶入哪些海域，设置仪表盘，安排测试，在风浪里判断是否返航。</p>
<p>但这个比喻也有一个危险的地方。</p>
<p>船长不能因为自己不亲手焊接发动机，就完全不懂发动机。否则仪表盘坏了，船偏离航线时，他可能连问题发生在哪里都不知道。</p>
<p>所以问题远远比我们想象中的复杂，在这个 AI 快速发展的时代，现在留下的定论也许很快就会被推翻，一切变得扑朔迷离，难以捉摸...</p>
<h2>也许应该把它当成一个实验</h2>
<p>antirez 的话之所以有冲击力，是因为他说得很绝对。</p>
<p>绝对的表达容易传播，也容易让人站队。但真正落到项目里，变量太多了：项目规模、代码质量、模型能力、测试体系、开发者经验、失败成本，都会改变答案。</p>
<p>也许我们现在应该尝试的是：</p>
<p>先写清楚设计，再让 AI 实现；用测试、质量保证、类型系统和运行结果去验证；人工重点检查公共接口、关键路径和高风险的核心逻辑。</p>
<p>至于普通的局部实现，逐行阅读可以从“必做动作”降级为“发现异常时再做”。</p>
<p>这不一定适合每个人，也不一定适合每个项目。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/0a3b34192f38bb3c7f82db46d46bf673.webp" alt="值得审查的选择台"></p>
<p>无论如何，这个问题值得我们每一个开发者思考：</p>
<p>当 AI 生成代码的速度已经超过我们阅读代码的速度，开发者还要把一天的时间平均切成两半，一半用来产生新想法，一半用来追赶 人工智能 生成的文本吗？</p>
<p>也许我们真正需要重新设计的，不是代码审查本身，而是“什么值得审查”。</p>
<p>写到这里，我自己也有点迷糊了，让子弹再飞一会吧！</p>
<hr>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/95ad239eab6a898699ec13740ddebb81.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[CSS 文字高亮扫光，为什么看起来会动？]]></title>
            <link>https://i5lin.top/blog/post/4933612346</link>
            <guid isPermaLink="false">4933612346</guid>
            <pubDate>Mon, 20 Jul 2026 22:34:43 GMT</pubDate>
            <description><![CDATA[这篇文章从一个可交互学习网站出发，拆解 shimmer text 的 CSS 实现：渐变背景、background-clip、background-position 动画，以及倾斜高亮进入文字前需要预留的安全距离。]]></description>
            <content:encoded><![CDATA[<p>hello！大家好！</p>
<p>shimmer text 是一种很常见的 CSS 文字高亮扫光效果：文字本身不动，一条亮色渐变从字形里扫过去，让它看起来像被光擦了一下。</p>
<p>你可能在 loading 文案、AI 正在生成的提示、产品首页 hero、价格标签里都见过它。效果很小，但如果写得舒服，页面会多一点“正在运行”的感觉。</p>
<p>现在 AI 已经能帮我们生成很多代码了，但我反而更觉得，一些基础技术还是值得自己弄懂。至少要做到**知其然！**看到一个 CSS 动效时，知道它大概由哪些属性拼起来，出了问题也知道从哪里拆。</p>
<p>所以这篇先不急着贴页面和仓库。我会先一步步拆 shimmer text 的原理：渐变怎么做出来，为什么能裁进文字，背景为什么要移动，倾斜高亮为什么还要算安全距离。</p>
<p>专门的教程页面和源码仓库我放在文末，读完原理以后再打开看，会更顺。</p>
<h2>这个效果叫什么</h2>
<p>一般可以叫它：</p>
<ul>
<li><code>shimmer text</code></li>
<li><code>text shimmer</code></li>
<li><code>gradient text shimmer</code></li>
<li>或者更直白一点：文字高亮扫光效果</li>
</ul>
<p>它经常出现在这些场景里：</p>
<ul>
<li>loading 状态里的文字或骨架屏；</li>
<li>AI 产品里“正在生成”的提示；</li>
<li>首页 hero 里的重点短句；</li>
<li>产品价格、功能标签、活动入口里的强调文案；</li>
<li>需要一点“正在运行”感觉的交互界面。</li>
</ul>
<p>我个人会比较克制地用它。</p>
<p>因为 shimmer 很容易从“有质感”滑到“过度动效”。如果整个页面到处都在闪，用户的注意力会被拖着走，阅读会变累。我的倾向是：<strong>只在一个短句、一个状态、一个核心 CTA 周围使用它。</strong></p>
<h2>先一步步拆开原理</h2>
<p>先把页面和工具放一边，单看这个效果本身。</p>
<p>它背后其实有三层东西：</p>
<ul>
<li>文字本身；</li>
<li>一张比文字更宽的渐变背景；</li>
<li>把背景裁剪进文字形状的 <code>background-clip:text</code>。</li>
</ul>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/aea87e4f96b6a90554368857790acfd1.png" alt=""></p>
<p>这张图基本就是它的工作方式：小黑在后面推一条高亮带，文字像窗口一样，只露出背景穿过字形的那一部分。</p>
<h2>第一步：先做一条静态高亮背景</h2>
<p>先不考虑文字，也不考虑动画。</p>
<p>我们只需要一条渐变：</p>
<p>-- language: css --</p>
<pre><code class="language-css">.shimmer {
  background-image: linear-gradient(
    80deg,
    #8a8a8a,
    #ffffff,
    #8a8a8a
  );
}
</code></pre>
<p>这一步只是准备材料。</p>
<p>#ffffff 是最亮的高光，左右两侧的灰色负责让它过渡得自然一点。shimmer 的质感，通常来自这个亮区的宽度：太宽会像一大片灯扫过去，太窄又会显得硬。</p>
<p>在 demo 里，我把高亮宽度做成了可调参数。这样读者可以直接看到：<strong>高亮区域越窄，效果越像一道光；高亮区域越宽，效果越像一块亮背景。</strong></p>
<h2>第二步：把背景裁剪进文字</h2>
<p>这里进入关键 CSS：</p>
<p>-- language: css --</p>
<pre><code class="language-css">.shimmer {
  color: transparent;
  background-image: linear-gradient(
    90deg,
    #ff4d6d,
    #ffd166,
    #06d6a0,
    #4cc9f0,
    #8a5cf6
  );
  background-clip: text;
  -webkit-background-clip: text;
}
</code></pre>
<p>这里有三个点要一起看：</p>
<ul>
<li><code>color: transparent</code>：把原本的文字填充色藏起来；</li>
<li><code>background-image</code>：给文字背后放一张背景；</li>
<li><code>background-clip: text</code>：只在文字字形范围里显示背景。</li>
</ul>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/dc96e70e1e161a4495db55129dc02ed1.png" alt=""></p>
<p>这时候读者看到的“字”，其实是背景图透过字形露出来的区域。</p>
<p>这里也有一个小坑。</p>
<p>如果你前面已经设置了 background-clip:text，后面又写了一次 background: ...，background 这个 shorthand 可能会把一些 background 相关属性重置掉。为了少踩坑，我更推荐在这个效果里使用 background-image，再单独写 background-size、background-position。</p>
<h2>第三步：让背景比文字更宽</h2>
<p>如果背景和文字一样宽，动画空间会很紧。</p>
<p>高亮带刚开始移动，就可能已经压在第一个字母上了。restart 的时候尤其明显：你以为动画会从最左侧外面进入，结果高亮一刷新就贴在文字上。</p>
<p>所以 demo 里用了：</p>
<p>-- language: css --</p>
<pre><code class="language-css">.shimmer {
  background-size: 400% 100%;
}
</code></pre>
<p>意思是：背景的宽度是文字可见区域的 4 倍。</p>
<p>这样高亮可以从文字左侧外面进入，穿过文字，再从右侧离开。文字本身不动，动的是这张更大的背景。</p>
<h2>第四步：动画移动 background-position</h2>
<p>动画部分其实很短：</p>
<p>-- language: css --</p>
<pre><code class="language-css">.shimmer {
  animation: shimmer var(--speed) ease-in-out infinite both;
}

@keyframes shimmer {
  0% {
    background-position: var(--start-position) 0;
  }

  72%,
  100% {
    background-position: var(--end-position) 0;
  }
}
</code></pre>
<p>重点是 background-position。</p>
<p>0% 是背景起点，72% 到 100% 是收尾段。后面留一点时间不继续移动，动画循环时会更稳，不会像机械循环一样马上跳回来。</p>
<p>这个处理很小，但体感明显。</p>
<p>我在 demo 里还加了 pause、restart 和 speed 控制。它们方便观察参数变化：当速度变慢、角度改变、宽度收窄时，背景移动的边界会变得更容易看出来。</p>
<h2>第五步：为什么要算 safeDistance</h2>
<p>做到这里，普通 shimmer 已经能跑了。</p>
<p>让我多花时间的是这个问题：</p>
<blockquote>
<p>点击 restart 时，怎样让高亮真的从文字外侧进入？</p>
</blockquote>
<p>如果高亮是水平的，事情还算直观。多留一点左右距离就行。</p>
<p>但这次我们希望高亮是 45deg 倾斜的。倾斜以后，高亮带会因为文字高度产生额外横向投影。说得直白一点：同样一条斜线，要保证它完全不碰到文字，就要比水平情况多留一些距离。</p>
<p>于是 demo 里算了一个 safeDistance：</p>
<p>-- language: js --</p>
<pre><code class="language-js">const angle = 45 * Math.PI / 180

const safeDistance =
  (shineWidth + textHeight * Math.abs(Math.cos(angle)) / 2)
  / Math.abs(Math.sin(angle))
</code></pre>
<p>这里不用把它想得太复杂。</p>
<p>我们只是根据：</p>
<ul>
<li>高亮带宽度 <code>shineWidth</code>；</li>
<li>文字高度 <code>textHeight</code>；</li>
<li>高亮角度 <code>angle</code>；</li>
</ul>
<p>算出一段额外的水平移动距离。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/c7dd3b84184be1bdb8afda695c4b3593.png" alt=""></p>
<p>demo 里蓝色虚线和黄色安全范围就对应这个关系：蓝色范围表示文字可见区域，黄色范围表示高亮为了完整进出，需要额外走过的区域。</p>
<p>如果你把角度调得更斜，或者把高亮宽度调得更大，安全距离就会变大。这个关系可以用三角函数解释，实际工程里把它封装成一个计算函数就够了。</p>
<h2>最小版本可以这样写</h2>
<p>一份比较完整的最小版本大概是这样：</p>
<p>-- language: html --</p>
<pre><code class="language-html">&lt;p class=&quot;shimmer&quot;&gt;CSS is awesome, right?&lt;/p&gt;

&lt;style&gt;
  .shimmer {
    --speed: 2.4s;
    --angle: 45deg;
    --shine-width: 120px;
    --start-position: 72%;
    --end-position: 28%;

    display: inline-block;
    color: transparent;
    font: 700 48px/1.1 system-ui;
    background-image: linear-gradient(
      var(--angle),
      #8a8a8a calc(50% - var(--shine-width)),
      #ffffff 50%,
      #8a8a8a calc(50% + var(--shine-width))
    );
    background-size: 400% 100%;
    background-position: var(--start-position) 0;
    background-clip: text;
    -webkit-background-clip: text;
    animation: shimmer var(--speed) ease-in-out infinite both;
  }

  @keyframes shimmer {
    0% {
      background-position: var(--start-position) 0;
    }

    72%,
    100% {
      background-position: var(--end-position) 0;
    }
  }
&lt;/style&gt;
</code></pre>
<p>如果只想做一个静态展示，这些就够了。</p>
<p>如果想像 demo 一样支持角度、宽度、restart、pause，就需要再加一点 JS，动态更新 CSS 变量。我的建议是：先把 CSS 版本跑通，再做控制器。不要一上来就把调参面板、localStorage、教程轮播全塞进去。</p>
<h2>我会注意的几个细节</h2>
<p>这个效果写起来不难，影响质感的是细节：</p>
<ul>
<li><strong>高亮不要太宽</strong>：短句里 2 个字母左右通常比较舒服。</li>
<li><strong>角度不要乱跳</strong>：<code>45deg</code> 很稳定，识别度也好。</li>
<li><strong>背景要足够大</strong>：<code>background-size: 400% 100%</code> 是一个好起点。</li>
<li><strong>restart 要从外面进</strong>：不要只重置动画，还要确认 <code>start-position</code> 真的在文字外。</li>
<li><strong>正文阅读场景慎用</strong>：长段落一直闪，读者会累。</li>
</ul>
<p>我个人觉得，shimmer text 最适合放在“状态变化”附近。</p>
<p>比如 AI 正在生成、模型正在思考、结果即将出现。它传达的是一种轻微的进行中状态。放在纯装饰场景里也能看，但容易喧宾夺主。</p>
<h2>最后</h2>
<p>这个小 demo 对我来说有一个挺有意思的点：很多 CSS 动效看起来像视觉技巧，拆开以后其实是几个很朴素的概念在配合。</p>
<p>linear-gradient() 做材料，background-clip:text 做裁剪，background-position 做移动，再用一点几何计算处理边界。</p>
<p>读者如果只想拿走一个结论，我会建议记住这句：<strong>shimmer text 的文字通常不动，动的是文字背后的背景。</strong></p>
<p>先写到这里。后面如果我继续把这个 demo 做成更完整的 CSS 小课，可能会再补一篇，专门聊“如何把视觉效果做成可学习的交互说明”。</p>
<h2>相关链接</h2>
<p>专门的教程页面在这里：</p>
<p><a href="https://aaronconlon.github.io/shimmer-text-demo/">CSS Shimmer Text Demo</a></p>
<p>源码也放到了 GitHub：</p>
<p><a href="https://github.com/AaronConlon/shimmer-text-demo">AaronConlon/shimmer-text-demo</a></p>
<p>这个页面一开始只是想复刻一个文本闪光效果，后来越做越像一个小教学工具：可以调速度、调高亮宽度、调角度，可以看完整背景，也可以看文字可见范围和计算后的安全范围。</p>
<hr>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/95ad239eab6a898699ec13740ddebb81.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[TanStack 的崛起（下）：竞品、Issue 与未来野心]]></title>
            <link>https://i5lin.top/blog/post/4933598496</link>
            <guid isPermaLink="false">4933598496</guid>
            <pubDate>Mon, 20 Jul 2026 22:31:57 GMT</pubDate>
            <description><![CDATA[继续比较 TanStack 与前端竞品，分析公开 Issue 中的工程取舍、团队响应方式，以及它走向应用基础层与 AI agent 工具链的可能性。]]></description>
            <content:encoded><![CDATA[<p>hello！大家好！</p>
<p>上篇我们从 React Query 出发，走到了 TanStack 的产品矩阵。本篇继续往下看：当它面对 SWR、RTK Query、Apollo、React Router 和 Next.js 等方案时，究竟把复杂度放在了哪里；公开 Issue 又如何暴露它的优势与代价。</p>
<h2>和前端竞品放在一起看</h2>
<p>比较工具时，最容易陷入“谁的 API 更漂亮”，但这种看法非常主观，千人千面，很多时候谁也说服不了谁。更实用的方式是看它们把复杂度放在哪里。</p>
<h3>数据请求：SWR、RTK Query、Apollo 与 TanStack Query</h3>
<p><a href="https://swr.vercel.app/">SWR</a> 的优势是轻量和直接，适合希望用较少抽象实现缓存与重新验证的项目。它的心智模型清楚，和 React 组件组合自然。TanStack Query 的范围更大，除了查询缓存，还把 mutation 生命周期、离线、持久化、开发工具和多框架适配纳入体系。项目越复杂，后者的统一性越有价值；项目越简单，SWR 的轻量可能更舒服。</p>
<p><a href="https://redux-toolkit.js.org/rtk-query/overview">RTK Query</a> 与 Redux Toolkit 绑定得更紧。如果团队已经把 Redux 作为全局状态中心，RTK Query 可以让接口数据进入同一套 store、middleware 和规范中。TanStack Query 则坚持把服务器状态从客户端 store 中分离出来。两者没有绝对高下，区别在于团队是否希望所有状态共享一套 Redux 基础设施。</p>
<p><a href="https://www.apollographql.com/docs/react/">Apollo Client</a> 解决的是更明确的 GraphQL 场景：它会围绕 schema、实体缓存和查询文档建立一整套客户端模型。拿它直接和 TanStack Query 比，容易忽略数据协议的差异。TanStack Query 更像通用的异步状态层，Apollo 则更像 GraphQL 应用的专用客户端。</p>
<h3>路由与全栈：React Router、Next.js 与 TanStack Router</h3>
<p><a href="https://reactrouter.com/">React Router</a> 的优势是生态广、历史长、团队认知成本低，并且已经覆盖数据路由与框架模式。TanStack Router 的差异在于类型安全更彻底：路由树、路径参数、搜索参数和 loader 数据之间的关系，尽量在编译阶段暴露问题。</p>
<p><a href="https://nextjs.org/docs/app">Next.js App Router</a> 则是更完整的应用框架，服务端组件、部署平台和约定式工程能力都更成熟。TanStack Router 与 Start 选择的是更可组合、更靠近 Vite 和运行时的路线。前者适合希望快速获得完整平台的团队，后者适合希望保留更多基础设施选择权、并把路由和数据流作为核心设计的团队。</p>
<p>这个选择也对应 TanStack 的长期风险：类型安全与可组合性越强，学习曲线和类型复杂度越高；框架越年轻，生态和文档稳定性就越需要时间验证。</p>
<h3>表格：AG Grid、MUI Data Grid 与 TanStack Table</h3>
<p><a href="https://www.ag-grid.com/">AG Grid</a> 和 <a href="https://mui.com/x/react-data-grid/">MUI Data Grid</a> 更强调完整组件体验，适合希望快速得到排序、筛选、分页、编辑和主题能力的团队。TanStack Table 把渲染层留给应用，因此能适配更多设计系统，但也把无障碍、键盘行为、空状态和性能细节交还给开发者。</p>
<p>换句话说，TanStack Table 的“自由”不是免费的。它的价值不在于让所有项目都少写代码，而在于当产品需要特殊的交互和视觉约束时，底层引擎不会成为天花板。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/4917bffd771e3f0da92e96dd5f199d1e.webp" alt="小黑比较不同工具的适用边界"></p>
<h2>从公开 Issue 看团队如何处理问题</h2>
<p>开源项目真正的工程能力，往往要到问题发生时才看得出来。TanStack 的 GitHub Issue 有一个很明显的特点：维护者经常不急着把所有反馈都定义成 bug，而是先解释行为背后的模型，再给出迁移方式、临时方案或明确的“不计划支持”。这让讨论不一定总是让提问者满意，但通常能留下可复用的上下文。</p>
<h3>Query 的 hydration 争议：正确性优先于旧行为</h3>
<p>在 <a href="https://github.com/TanStack/query/issues/6240">Issue #6240</a> 中，用户反馈 v5 的 HydrationBoundary 和 v4 的 Hydrate 行为不同：客户端切换页面时，缓存数据似乎更新得太晚，页面会先使用旧数据渲染。</p>
<p>维护者没有简单把它当作回归修掉，而是解释了这个变化的原因：v4 可能在 React transition 完成前就修改当前页面的缓存，甚至在 transition 被取消后留下可观察的副作用；v5 选择把 hydration 推迟到渲染之后，接受短暂的不完美，以避免在 render 阶段修改外部缓存。对于直接读取缓存但没有建立 observer 的用法，维护者也明确指出这不是推荐模式，并建议使用 useQuery 或自行保留底层 hydrate 方案。</p>
<p>这个案例的价值不在于 v5 一定没有代价，而在于它展示了一种取舍：当 React 的并发模型与旧缓存行为冲突时，团队选择了更容易解释的正确性，并给高级用户留下低层 API。这样的答复比“升级到最新版即可”更有长期价值，但也意味着迁移者需要真正理解数据订阅模型。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/2458f5208eba655839ca72c56194be75.webp" alt="小黑守住 hydration 的渲染闸门"></p>
<h3>浏览器兼容：支持范围就是产品边界</h3>
<p>另一个更直接的例子是 <a href="https://github.com/TanStack/query/issues/6371">Issue #6371</a>。用户在 Safari 14 中升级 Query v5 后遇到私有类字段语法错误。维护者的判断很明确：v5 的支持范围从 Safari 15 开始，Safari 14 不属于默认支持的浏览器；如果项目必须兼容旧浏览器，就让构建工具转译依赖，或者暂时停留在 v4。</p>
<p>这不是一个讨喜的答案，却是一种清晰的工程沟通。团队没有为了覆盖所有环境而牺牲包体积和现代语法，也没有把“浏览器支持”隐藏在实现细节里。开源库的使用者因此可以根据目标用户做选择，而不是在生产环境里才发现隐含条件。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/7489335c935d00382ac7b67c129ee7e7.webp" alt="小黑检查浏览器支持门槛"></p>
<h3>类型变更：修复很快，但兼容策略仍有争议</h3>
<p>在 <a href="https://github.com/TanStack/query/issues/9660">Issue #9660</a> 中，v5.89.0 的 mutation 回调类型发生变化，onSuccess、onError 和 onSettled 的参数位置与类型让一些已有代码无法编译。维护者先解释新增参数与已有上下文的关系，随后提交修复，并继续追问低层封装如何同时兼容不同版本。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/3f7611f72d80d7c41b4e86eef5d5ee34.webp" alt="小黑给新类型合同接上迁移插头"></p>
<p>这个案例比前两个更能说明 TypeScript 库的现实：运行时没有变化，不代表类型层没有破坏性影响。TanStack 文档把类型修复视为可以随补丁版本发布的变化，这有助于持续改善类型推导，但对构建二次封装的团队并不总是轻松。它的优势是维护者能够快速响应类型问题，代价是升级时需要锁定补丁版本并认真阅读变更。</p>
<h3>Router 与 Start：快速迭代带来的基础设施风险</h3>
<p>Router 生态中的 <a href="https://github.com/TanStack/router/issues/3367">Issue #3367</a> 讨论了近期版本的非流式 SSR hydration 问题，Issue 最后指向已有 Discussion，而不是在问题页面里继续堆叠一个孤立修复。这种处理方式体现了项目在把具体故障放回更大的 SSR 讨论中，但对只想得到“哪个版本能用”的使用者来说，信息分散也会增加查找成本。</p>
<p>Start 的问题则更容易触及运行时边界。比如 <a href="https://github.com/TanStack/router/issues/7402">Issue #7402</a> 记录了持续 SSR 负载下的内存增长：报告者通过压测区分出 QueryClient 的 gcTime 保留和原生流缓冲两个路径，其中一个可以通过 gcTime: 0 缓解，另一个仍需要进一步处理。这个 Issue 后来关闭，但正文保留了复现条件、内存数据和 bisect 结果。</p>
<p>这里能看出 TanStack 和成熟大框架的差异。TanStack 更常把问题拆成可验证的技术假设，鼓励用户提供最小复现和测量结果；它不一定立刻给出一个封装好的答案，但会让问题边界变得清楚。对于早期的全栈框架，这种公开的工程过程很有价值，也提醒使用者：RC 产品不应该被当成已经完成所有生产验证的基础设施。</p>
<h3>Table 的重渲染：headless 不等于自动优化</h3>
<p>在 <a href="https://github.com/TanStack/table/issues/4794">Issue #4794</a> 中，用户反馈可编辑表格会出现大量行和单元格重渲染。讨论里出现了 useMemo、拆分并记忆 Row/Cell、避免不稳定的渲染函数等多种方案，后续评论还指出某些 flexRender 用法会导致组件被卸载再挂载。</p>
<p>这件事不能简单归咎于库“性能不好”。TanStack Table 把渲染层交给应用，应用也就必须负责稳定列定义、稳定数据引用、行级记忆和虚拟化策略。headless 的优势是没有固定组件层，代价是性能模型需要由使用者掌握。它适合愿意理解渲染链路的团队，不适合把所有细节都交给组件库的项目。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/1d924f8707224b06edcdc07b92dfc881.webp" alt="小黑排查公开 Issue"></p>
<h2>TanStack 为什么能迅速发展</h2>
<p>把这些产品与 Issue 放在一起看，TanStack 的增长并不只是因为某个库碰巧流行，而是因为它形成了几条相互强化的路径。</p>
<h3>一套连续的心智模型</h3>
<p>Query 用查询键描述数据身份，Router 用路由树描述页面边界，Table 用状态模型描述数据视图。它们都在做同一件事：把隐含在组件和副作用里的关系，变成可以组合、可以观察、可以被类型系统检查的结构。</p>
<p>开发者从 Query 进入后，很容易理解 Router 的 loader、Table 的受控状态和 Virtual 的可见范围。产品之间不必完全绑定，但学习收益可以迁移，这比单纯增加包数量更重要。</p>
<h3>Headless 与可移植性</h3>
<p>TanStack 不急于提供一套统一视觉组件，而是把核心行为和渲染层拆开。这让它能适配不同框架、设计系统和运行时，也减少了“换一个 UI 库就要重写业务逻辑”的成本。</p>
<p>当然，headless 也会把实现责任交还给应用。TanStack 的强项不是替你做完所有事情，而是让底层模型足够清楚，允许你在不同产品约束下继续组合。</p>
<h3>TypeScript 不只是类型提示</h3>
<p>在 Router 里，类型系统是路由合同；在 Query 里，它约束查询键、函数返回值和 mutation 参数；在 Table 和 Form 里，它帮助字段、列和表单状态保持一致。类型因此不再只是编辑器里的辅助信息，而成为库设计的一部分。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/508adfe687d6947c8f61bfe8e119ac96.webp" alt="小黑检查类型合同"></p>
<p>这也是 TanStack 与一些“运行起来就行”的工具的差异。它愿意把复杂度提前放进类型系统，换取大型项目在重构时更早暴露问题。代价是类型错误可能很长，升级时也更容易遇到补丁版本带来的编译变化。</p>
<h3>公开的产品节奏</h3>
<p>官网把不同产品标注为 stable、RC、beta 或 alpha，GitHub Issue 里也经常保留设计背景、复现步骤和维护者判断。它没有消除风险，但让风险更可见。</p>
<p>这套节奏还带来一个社区效应：使用者不只是等待一个封闭产品交付，而是可以通过 Issue、Discussion、赞助、文档和示例参与产品形成。对于一个以开源为核心的团队，这种持续的公开反馈比一次性宣传更能积累信任。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/96b2fa030c80ee32055affe364552f59.webp" alt="小黑把共同原则拼成工作台"></p>
<h2>TanStack 的野心与展望</h2>
<p>TanStack 现在的产品矩阵，已经从“几个好用的 React 库”走向“应用开发中常见边界的一组基础设施”：数据获取、路由、全栈运行、表格表单、性能、开发工具，以及 AI agent 的使用方式。</p>
<p>这种野心有三个方向。</p>
<p>第一，它想把应用状态、URL 状态和服务器状态放进一套可以相互连接的模型里。Query 负责数据缓存，Router 负责页面与 URL，Start 负责服务器边界，三者组合后，应用可以在预取、渲染、流式输出和客户端更新之间减少重复胶水代码。</p>
<p>第二，它想保持基础设施的可替换性。与绑定某个部署平台或 UI 体系相比，核心库、适配器和运行时输出的分层更适合多框架、多运行时的前端世界。这也是它持续强调 framework agnostic、type-safe 和 no vendor lock-in 的原因。</p>
<p>第三，它开始为 agent 时代准备工具链。CLI 和 Intent 目前还不能与 Query、Table 的成熟度相提并论，但它们表达了一个判断：未来的开发者体验，不只是让人更快写代码，也要让自动化工具能理解项目结构、安装能力、遵循约束并完成验证。</p>
<p>不过，TanStack 也必须面对自己的扩张风险。产品越多，版本和文档之间的组合越复杂；Start、AI、DB、Store 等新项目还需要时间证明稳定性；headless 的自由会带来更多应用级责任；类型系统的优势也可能变成学习门槛。</p>
<p>所以，TanStack 的下一阶段不只是继续发布更多项目，而是要证明这些项目组合起来确实比各自为战更好用。如果它能在保持可移植性的同时，进一步降低全栈配置、SSR 调试、状态同步和 agent 使用的成本，那么它就可能从“优秀的开源工具集合”变成前端应用的通用基础层。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/ce86db68f7bb3d3d6fac7e96e3b33f3d.webp" alt="小黑把产品装上应用栈之船"></p>
<h2>最后</h2>
<p>TanStack 的崛起，核心不在于它覆盖了多少产品，而在于它一直在尝试用同一套工程原则处理不同层次的问题：明确状态边界、强化类型合同、拆开核心与渲染、公开稳定性和取舍。</p>
<p>从 React Query 开始，它先解决了服务器状态的混乱，再把这套方法延伸到路由、全栈、数据视图与开发工具。未来它能走多远，取决于产品之间的组合是否足够稳定，也取决于这些抽象能否继续给真实项目带来比额外复杂度更多的收益。</p>
<p>感谢阅读这篇文章，我们下期再见。</p>
<hr>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/95ad239eab6a898699ec13740ddebb81.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[TanStack 的崛起（上）：从 React Query 到前端应用栈]]></title>
            <link>https://i5lin.top/blog/post/4933598458</link>
            <guid isPermaLink="false">4933598458</guid>
            <pubDate>Mon, 20 Jul 2026 22:31:56 GMT</pubDate>
            <description><![CDATA[从 React Query 的服务器状态出发，梳理 TanStack 如何扩展到 Query、Router、Start、Table、Virtual 等前端产品，并解释它走向应用栈的第一步。]]></description>
            <content:encoded><![CDATA[<p>hello！大家好！今天，我给大家分享一个我超级喜欢的技术栈的故事：TanStack!</p>
<p>前端工具的竞争，表面上是 API 的竞争，底层其实是对复杂度的重新分配：哪些事情交给框架，哪些事情交给应用，哪些事情应该由类型系统提前约束。</p>
<p>一个库能不能长期留下来，往往不只取决于它能不能解决一个问题，也取决于它能不能在下一个问题出现时继续保持同一套判断。</p>
<p>今天，让我们一起来了解一下 TanStack 的技术“史诗”！</p>
<p>它最初因为 React Query 被更多开发者认识，后来逐步扩展到路由、全栈框架、表格、表单、虚拟列表、AI 与开发工具。今天的 <a href="https://tanstack.com/">TanStack</a> 已经不太像一组彼此独立的工具，而更像一套围绕 TypeScript、数据流和应用边界组织起来的前端应用栈。</p>
<p>让我们沿着产品技术栈一步步向前走：React Query 解决了什么，TanStack 为什么要改名，竞品各自把复杂度放在哪里，以及这些公开 Issue 如何暴露出它的优势与代价。</p>
<h2>React Query 先解决了什么</h2>
<p>在 React Query 出现之前，前端获取数据通常会混合几种工作：组件启动请求、显示 loading、处理错误、保存结果、判断结果是否过期、响应窗口重新聚焦、处理重复请求，以及在修改数据后让相关页面重新获取数据。</p>
<p>这些工作并不难单独完成，难的是它们会在每个页面重复出现。一个页面写一套 useEffect，另一个页面写一套全局状态，最后大家都要回答同样的问题：这份数据现在属于谁？多久算旧？两个组件同时请求同一个资源时，能不能复用结果？修改完成后，哪些缓存需要失效？</p>
<p>这些问题贯穿着 React 项目的方方面面，无论开发者水平是高是低，都需要直接面对这个问题。在一个团队里，给出一份稳定、高效且让大家都满意的方案并不简单。</p>
<p>React Query 的重要变化，是把问题从“如何在组件里发请求”改成“如何声明一份服务器状态”。在今天的 <a href="https://tanstack.com/query/latest">TanStack Query</a> 里，查询键、查询函数和缓存生命周期组成了一个相对完整的模型。</p>
<p>最小的使用方式看起来很简单：</p>
<p>-- language: tsx --</p>
<pre><code class="language-tsx">const todosQuery = useQuery({
  queryKey: [&#39;todos&#39;],
  queryFn: fetchTodos,
})
</code></pre>
<p>少写几行请求代码价值并不大，这几个概念被固定了下来才是重头戏：</p>
<ul>
<li><code>queryKey</code> 是缓存的身份，也是依赖关系的声明；</li>
<li>查询结果可以在组件之间共享，并自动去重；</li>
<li>新数据可以在后台刷新，页面不必每次都回到空白 loading；</li>
<li>mutation 可以和失效、乐观更新、回滚联系起来；</li>
<li>缓存、重试、离线和开发调试都有统一入口。</li>
</ul>
<p>以上功能涉及到了 Web 开发的方方面面，每一个都可以单独拉出来开发一个 npm 包，但是 Tanstack Query 直接给我们提供了一个完整的、强类型的无敌解决方案！</p>
<p>这让“服务器状态”和“客户端状态”开始分开。输入框是否展开、弹窗是否打开，仍然适合交给组件状态或状态管理库；来自接口、会过期、可能被别的用户修改的数据，则应该有自己的缓存和同步机制。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/2585549f3bcbdc637dd151083a3d2261.webp" alt="小黑整理服务器状态"></p>
<p>React Query 最早的影响，正是让很多团队意识到：不是所有状态都应该塞进 Redux，也不是所有异步逻辑都应该散落在副作用里。</p>
<h2>从 React Query 到 TanStack Query</h2>
<p>2022 年发布的 v4 是一次关键转折。官方在 <a href="https://tanstack.com/blog/announcing-tanstack-query-v4">v4 发布文章</a> 中说明，项目从过去的 React 专属库，重构为无框架核心加框架适配器的形式，并开始支持 Vue、Svelte 和 Solid。</p>
<p>这次改名不是简单换一个 npm 包名。它改变了项目的边界：核心缓存、查询客户端、订阅和状态转换尽量不依赖具体 UI 框架；React 的 hook、Vue 的组合式接口，属于上层适配器。这样做有两个直接收益。</p>
<p>第一，维护者可以复用同一套数据逻辑。第二，用户学到的不是某个框架的技巧，而是一套可以迁移的服务器状态模型。v4 还加入了更完整的离线支持、持久化和 React 18 兼容能力，说明它已经不满足于“帮你发请求”，而是在争取成为异步状态的基础设施。</p>
<p>到了 v5，项目继续收紧 API，拥抱更现代的 TypeScript 和 React 并发模型。这种演进方式也埋下了 TanStack 的一个特征：它愿意为了长期模型调整短期兼容性，但会把迁移说明、类型边界和取舍放到台面上讨论，团队开发人员在 github 上发布了大量解释性回复，同时也写了很多实用的教程和博客。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/a15b7ea9816d8ab58924f68bf88ebd0c.webp" alt="小黑把 React Query 挂上跨框架桥"></p>
<h2>按产品技术栈看 TanStack</h2>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/fcecc5d46553d427e2094a093684a0e7.png" alt="TanStack 产品矩阵"></p>
<p>上图是 TanStack 当前产品矩阵的一个概览。它的扩张不是简单地“看到什么热门就做什么”，大致可以按五层来理解。</p>
<h3>框架层：Router 与 Start</h3>
<p><a href="https://tanstack.com/router/latest">TanStack Router</a> 是这套体系的路由基础。它把路由树、路径参数、搜索参数、loader、预加载和 pending 状态纳入类型系统。尤其是搜索参数，它不再只是一个字符串，而可以有明确的结构、解析和序列化规则。</p>
<p>这种设计很适合数据密集型应用：列表筛选、分页、排序和详情页状态都能映射到 URL，loader 又可以和查询缓存配合。路由因此不只是页面切换工具，而变成了应用数据流的一部分。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/1636137811a7ae25873b39f475d5bf08.webp" alt="小黑校准 Router 与 Start 的边界"></p>
<p>这种设计真的非常 nice，我们不用再去花时间精力找各种不同的解决方案。</p>
<p><a href="https://tanstack.com/start/latest">TanStack Start</a> 则在 Router 之上继续向全栈靠近。它提供服务端渲染、流式输出、服务端函数、服务端路由和面向不同运行时的构建结果。官方目前仍标记为 RC，这个状态很重要：它代表产品方向已经清楚，但基础设施仍处于快速打磨阶段。</p>
<h3>数据与状态层：Query、DB、Store 与 AI</h3>
<p>Query 是最成熟、最容易理解的一层：它负责远程数据的缓存、同步和更新。</p>
<p>DB 试图把客户端数据存储做得更具反应性，让接口数据可以在本地形成可查询、可订阅的关系。Store 更靠近一般客户端状态，但仍然延续轻量、类型友好的思路。AI 则把流式文本、工具调用、多模态和 agent 场景纳入同一套 TypeScript 接口。</p>
<p>官网当前将 DB 标为 beta、AI 标为 beta、Store 标为 alpha。把这些状态公开标出，既是风险提示，也是一种产品管理方式：用户可以清楚知道 Query、Router 这样的成熟工具，与新工具之间并不处在同一个稳定等级。</p>
<h3>UI 与体验层：Table、Form 与 Hotkeys</h3>
<p><a href="https://tanstack.com/table/latest">TanStack Table</a> 的定位很典型：它是 headless 的表格引擎，不替你决定 HTML、CSS 和组件外观。排序、筛选、分组、分页、行选择等能力由它提供，最终如何渲染由应用自己决定。</p>
<p>这条路线和“开箱即用”的表格组件不同。它牺牲了一部分上手速度，换来设计自由、组件库兼容性和更细的性能控制。Form 与 Hotkeys 也延续同样思路：提供状态和行为模型，不强迫应用接受一套视觉语言。</p>
<h3>性能层：Virtual 与 Pacer</h3>
<p>Virtual 负责大列表、大表格的窗口化渲染，让应用只渲染视口附近的数据。它和 Table 的组合很自然：Table 管理数据模型，Virtual 管理可见范围。</p>
<p>Pacer 更像是性能调度工具，用来处理节流、防抖、批处理和高频更新。它们共同说明 TanStack 的关注点正在从“有没有功能”转向“功能在复杂数据量和高频交互下是否还能保持可控”。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/3bc1838883fcb68759194d2567abd985.webp" alt="小黑拧紧可组合的产品栈"></p>
<h3>工具层：Devtools、Config、CLI 与 Intent</h3>
<p>Devtools 负责观察查询、路由和缓存；Config、CLI 和 Intent 则在把工程配置、项目初始化与 AI agent 的工作方式纳入产品范围。尤其是 <strong>Intent</strong> 这类工具，反映出维护者开始考虑一个新问题：未来不只有人会阅读文档和修改代码，agent 也需要可靠地发现、安装和使用这些能力。</p>
<h2>上篇先写到这里</h2>
<p>从 React Query 到产品矩阵，TanStack 的第一步是把服务器状态、路由、全栈边界和数据视图逐渐放进一套可组合的技术栈。下篇将继续从竞品和公开 Issue 出发，看看这些选择在真实工程里意味着什么。</p>
<hr>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/95ad239eab6a898699ec13740ddebb81.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.79]]></title>
            <link>https://i5lin.top/blog/post/4907204411</link>
            <guid isPermaLink="false">4907204411</guid>
            <pubDate>Fri, 17 Jul 2026 01:14:47 GMT</pubDate>
            <description><![CDATA[本期分享 StyleKit、Cuelume、Its Hover、Mole、FlowCV、MessAuto、react-hot-toast、Atuin、Flash 保存计划、BLEUnlock、storage-book、Deep Agents 实战、Wenyi 和 Codia。]]></description>
            <content:encoded><![CDATA[<p>各位读者，大家好。</p>
<p>在数字产品中，体验往往由许多小的反馈共同组成。一个清楚的界面、一次顺手的操作，都会影响用户对产品的判断。</p>
<p>本期主要内容：</p>
<ul>
<li>StyleKit —— 把网页风格、组件样式和提示词整理成可直接参考的资料库</li>
<li>Cuelume —— 给按钮、链接和操作结果加上轻量的交互声音</li>
<li>Its Hover —— 让图标随交互动作产生有目的的动画</li>
<li>Mole —— 在终端清理、卸载、分析和维护 Mac</li>
<li>FlowCV —— 用模板快速制作简历并导出 PDF</li>
<li>MessAuto —— 自动提取 Mac 上的短信和邮件验证码</li>
<li>react-hot-toast —— 轻量、可定制的 React 通知组件</li>
<li>Atuin —— 同步、搜索并备份终端历史</li>
<li>Flash 保存计划 —— 多年前玩过的《狂扁小朋友》重新出现在浏览器里</li>
<li>BLEUnlock —— 用 iPhone、Apple Watch 或其他蓝牙设备自动锁定和解锁 Mac</li>
<li>storage-book —— 从存储介质到文件系统，边学边实现 KnotFS</li>
<li>Deep Agents 实战 —— 从零学习构建生产级智能体</li>
<li>Wenyi —— 将长篇作品翻译成中文并保留书籍结构</li>
<li>Codia —— 把代码转换成适合分享和文档使用的图片</li>
</ul>
<h3>StyleKit</h3>
<p><a href="https://github.com/AnxForever/stylekit">StyleKit</a> 收录了 135 种视觉和布局风格，每种风格都配了颜色、字体、组件样式和提示词。还有页面示例、动画、页面模板，可以先看效果，再决定页面往哪个方向走。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/6f71a9bee0d3765ac553f934ce4f82ad.png" alt="StyleKit README 首页预览"></p>
<p>这类资料可以把抽象的风格描述转换成可查看的视觉参考。做页面时，与其只写“帮我做得高级一点”，不如同时提供颜色、字体、组件和示例，AI 更容易对齐目标。需要制作页面时，可以先浏览 <a href="https://stylekit.top">stylekit.top</a>。</p>
<h3>Cuelume</h3>
<p><a href="https://github.com/Danilaa1/cuelume">Cuelume</a> 准备了 14 种交互声音，声音由浏览器即时合成，不需要准备音频文件，也不增加运行时依赖。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/2129b3b2a7681af9282652b6b6fadec6.png" alt="Cuelume 示例页预览"></p>
<p>iPhone 里有好多种铃声：来电、短信、日历提醒、充电提示，每一种声音都在告诉你“发生了一件事”。如果网站也需要给按钮按下、操作完成、加载结束这些动作增加声音反馈，Cuelume 提供了一套比较轻的做法。</p>
<p>悬停、按下、松开、切换、成功、报错、加载完成，都可以用 <code>data-cuelume-*</code> 属性标记，再调用一次 <code>bind()</code>。示例页里还可以直接试听这些声音：<a href="https://cuelume-site.pages.dev/">cuelume-site.pages.dev</a>。</p>
<p>这个项目把“什么动作配什么声音”整理成了可以直接尝试的示例。对于需要给界面增加声音反馈的场景，可以先从这些预设开始，再根据产品的操作节奏进行调整。仓库里也提供了 <code>npm install cuelume</code> 的安装方式。</p>
<h3>Its Hover</h3>
<p><a href="https://github.com/itshover/itshover">Its Hover</a> 是一个基于 React 和 Motion 的动画图标库，提供 186+ 个图标，覆盖界面基础操作、社交、技术品牌和状态提示等场景。</p>
<p><img src="https://opengraph.githubassets.com/ede03e4389fe7c9e51591dff2585a0e4db49bfc539a49063a18f888ab54cf9b8/itshover/itshover" alt="Its Hover GitHub 预览"></p>
<p>图标支持复制源码后继续调整动画、描边宽度和颜色，也可以通过 <code>shadcn</code> 命令直接加入项目。悬停、点击、加载和状态变化都能配合动画，让图标承担一部分操作反馈。完整图标可以在 <a href="https://itshover.com/icons">itshover.com/icons</a> 中查看。</p>
<h3>Mole</h3>
<p><a href="https://github.com/tw93/mole">Mole</a> 是一个面向 macOS 的终端维护工具，提供清理缓存、卸载应用、分析磁盘、清理项目构建产物和查看系统状态等命令。</p>
<p><img src="https://opengraph.githubassets.com/1/tw93/mole" alt="Mole GitHub 预览"></p>
<p>常用入口包括 <code>mo clean</code>、<code>mo uninstall</code>、<code>mo analyze</code> 和 <code>mo status</code>，清理和卸载操作都提供 <code>--dry-run</code> 预览方式。它适合需要在终端集中处理 Mac 垃圾文件、应用残留和开发产物的场景，但涉及删除的命令仍应先检查预览结果。</p>
<h3>FlowCV</h3>
<p><a href="https://flowcv.com/">FlowCV</a> 是一个在线简历制作工具，提供 50 多个模板，可以导入已有简历内容、调整版式和设计，并导出无水印 PDF。</p>
<p><img src="https://prod.flowcvassets.com/public/flowcv/flowcv-og-image.png" alt="FlowCV 页面预览"></p>
<p>免费方案支持创建和下载一份简历，内容可以持续编辑，模板和版式也能继续调整。需要快速整理求职简历、制作不同版本，或者把已有 PDF、DOCX 内容重新排版时，可以用它先完成基础版本。</p>
<h3>MessAuto</h3>
<p><a href="https://github.com/LeeeSe/MessAuto">MessAuto</a> 是一个 macOS 应用，用 Rust 编写，可以自动提取短信和邮件中的验证码，并把验证码粘贴到当前输入框。</p>
<p><img src="https://opengraph.githubassets.com/1/LeeeSe/MessAuto" alt="MessAuto GitHub 预览"></p>
<p>它支持监听 macOS 的信息和邮件应用，提供自动粘贴、自动回车、不占用剪贴板和浮动提示等选项。使用前需要授予“完全磁盘访问权限”和“辅助功能”权限，并保持信息或邮件应用在后台运行。</p>
<h3>react-hot-toast</h3>
<p><a href="https://github.com/timolins/react-hot-toast">react-hot-toast</a> 是一个用于 React 的通知组件，支持自定义样式、Promise 状态通知、无障碍访问和无头式钩子，包含样式的体积小于 5KB。</p>
<p><img src="https://opengraph.githubassets.com/1/timolins/react-hot-toast" alt="react-hot-toast GitHub 预览"></p>
<p>安装后加入 <code>Toaster</code>，就可以在组件中调用 <code>toast()</code> 显示提示。它适合处理保存成功、请求失败、加载中等短暂反馈，不需要为每一种提示状态单独搭建一套弹窗结构。</p>
<h3>Atuin</h3>
<p><a href="https://atuin.sh/">Atuin</a> 用于同步、搜索和备份终端历史，支持端到端加密，也可以自托管同步服务。它支持 macOS、Linux 和 Windows，以及 bash、zsh、fish 等常见 Shell。</p>
<p><img src="https://atuin.sh/assets/turtle-retro-terminal-wide.png" alt="Atuin 终端预览"></p>
<p>除了按命令内容搜索，还可以按目录、退出码、执行耗时和主机名筛选历史记录。安装后运行 <code>atuin setup</code> 即可完成基础配置，适合经常在多台设备之间切换、又需要找回旧命令的开发环境。</p>
<h3>Flash 保存计划</h3>
<p><a href="https://flash.zczc.cz/">Flash 保存计划</a> 收集了可以直接在浏览器里运行的 Flash 时代网页内容。这里重新发现了多年前玩过的《狂扁小朋友》，熟悉的画面和操作方式一下子回来了。</p>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/7b9343e73f87360abfc43086c2aa711f.png" alt="《狂扁小朋友》游戏画面"></p>
<p>这类小游戏以前通常要安装 Flash 插件才能打开，现在通过网页就能重新体验。对小时候玩过的人来说，它更像一个可以直接打开的旧收藏夹；对没接触过的人，也能看到早期网页游戏那种简单直接的交互方式。</p>
<h3>BLEUnlock</h3>
<p><a href="https://github.com/ts1/BLEUnlock">BLEUnlock</a> 是一个 Mac 菜单栏工具，可以根据 iPhone、Apple Watch 或其他蓝牙低功耗设备的距离，自动锁定或解锁 Mac。</p>
<p><img src="https://opengraph.githubassets.com/1/ts1/BLEUnlock" alt="BLEUnlock GitHub 预览"></p>
<p>它不需要安装 iPhone 配套应用，选定附近设备后即可工作，也支持在锁定和解锁时执行脚本、唤醒屏幕和暂停媒体播放。首次配置需要授予蓝牙、辅助功能和钥匙串等权限，自动解锁类工具建议先确认使用场景和权限范围。</p>
<h3>storage-book</h3>
<p><a href="https://github.com/Lularible/storage-book">storage-book</a> 是一本从存储介质、Flash 物理到文件系统理论的开源技术书，也带着读者用约 840 行 C 代码实现教学级文件系统 KnotFS。</p>
<p><img src="https://opengraph.githubassets.com/1/Lularible/storage-book" alt="storage-book GitHub 预览"></p>
<p>全书从磨损、掉电和噪声这些底层问题讲起，再进入 FAT、日志结构、磨损均衡和 LittleFS 源码。适合想把“文件保存”这件事往硬件和实现层继续追下去的人。</p>
<h3>Deep Agents 实战</h3>
<p><a href="https://datawhalechina.github.io/deepagents-in-action/">Deep Agents 实战</a> 是一套围绕 LangChain、LangGraph 和 Deep Agents 的开源课程，内容从第一个智能体开始，逐步讲到虚拟文件系统、任务规划、子智能体、技能、长期记忆和沙箱执行。</p>
<p><img src="https://datawhalechina.github.io/deepagents-in-action/imgs/langchain-community.svg" alt="Deep Agents 实战课程页面"></p>
<p>它更像一套按工程问题组织的学习路线，而不是只展示一个能运行的对话示例。想了解智能体如何处理复杂任务、管理上下文和控制执行风险，可以从课程目录开始看。</p>
<h3>Wenyi</h3>
<p><a href="https://github.com/BigDawnGhost/wenyi">Wenyi</a> 是一个面向长篇作品的翻译工具，支持 EPUB、FB2、TXT、Markdown、HTML 和 PDF，并可以生成中文或双语版本。</p>
<p><img src="https://github.com/BigDawnGhost/wenyi/raw/main/docs/images/bilingual-preview.png" alt="Wenyi 双语电子书预览"></p>
<p>它会先分析整本书，再结合滚动上下文、术语表、润色和复核流程处理章节，适合需要保持人名、术语和叙事一致性的长篇翻译场景。项目目前仍在早期阶段，实际使用前需要准备模型服务和对应的密钥。</p>
<h3>Codia</h3>
<p><a href="https://codia.i5lin.top/">Codia</a> 可以把代码转换成适合文档、博客、社交媒体和自动化流程使用的高清图片，既提供页面操作，也提供方便 Agent 调用的 API。</p>
<p><img src="https://codia.i5lin.top/assets/gallery/html-neon-form.webp" alt="Codia 代码图片示例"></p>
<p>如果需要把一段代码整理成更容易阅读和分享的视觉内容，可以直接选择语言、样式和代码片段后生成图片，省去反复调整截图和排版的过程。</p>
<h2>最后</h2>
<p>持续关注前沿工具与优秀实践，是提升专业能力的重要途径。感谢您陪伴本期周刊，我们下期再见。</p>
<hr>
<p><img src="https://de4965e.webp.li/blog-images/2026/07/95ad239eab6a898699ec13740ddebb81.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[开发中使用第三方中转服务，真的安全吗？聊聊第三方中转服务的安全问题]]></title>
            <link>https://i5lin.top/blog/post/4241144542</link>
            <guid isPermaLink="false">4241144542</guid>
            <pubDate>Fri, 10 Apr 2026 18:51:50 GMT</pubDate>
            <description><![CDATA[省钱、省事之外，第三方中转到底还意味着什么？聊聊普通开发者最容易忽视的中转服务风险]]></description>
            <content:encoded><![CDATA[<p>大家好，昨晚我在冲浪的时候看到了一篇论文，其中说到了关于 Agent 服务的问题引起了我的担忧，今天就给大家分享一下吧！</p>
<h2>前言</h2>
<p>这一年来，越来越多的开发者开始接触甚至是大量使用 Agent AI。无论是写代码的助手、自动化运维工具、企业知识库问答，还是带有工具调用的智能体应用，很多项目都不再只是“调用一个大模型接口，然后输出一段文本”这么简单，而是开始让模型去执行命令、读写宿主环境的文件、访问数据库、调用浏览器和操作云端资源等等。</p>
<p>一旦走到这一步，很多开发者很自然会接触到这个东西：<code>第三方中转服务</code>。</p>
<p>它通常也叫做 API 中转、代理路由、统一网关、转发服务等。</p>
<p>说实话，我一开始找中转的原因也很简单：<strong>就当世最强几个模型来说，官方真的贵，而且容易遇到额度、速度或可用性问题。对普通开发者来说，中转看起来像是一个很自然的选择。</strong></p>
<p>国内中转，可以做到官方服务价格的一半甚至更低。一开始，我的关注点只有两个：</p>
<ul>
<li>价格是否合适</li>
<li>服务是否稳定</li>
</ul>
<p>但当我把它放进 Agent AI 的上下文，问题就不止是成本和可用性了，还会多出第三个更重要的问题：<code>安全性如何</code>？</p>
<p><img src="https://fastly.jsdelivr.net/gh/bucketio/img3@main/2026/04/11/1775841664963-4aa77719-41c6-47fd-b7df-2b2aaaa343f1.png" alt=""></p>
<p>就是一个经典的不可能三角，谁也没有把握能说自己能找到最完美的答案。</p>
<p>最近一篇论文《<a href="https://arxiv.org/html/2604.08407v1">Your Agent Is Mine: Measuring Malicious Intermediary Attacks on the LLM Supply Chain</a>》专门研究了这一点。</p>
<p>它的核心观点非常值得普通开发者了解：当 AI Agent 通过第三方中转服务调用模型时，这个中转节点并不只是“网络转发器”，而是一个拥有真实安全影响力的信任边界。</p>
<p><strong>在某些情况下，它甚至可以决定你的 Agent 最终执行什么命令。</strong></p>
<p>这听起来确实有点吓人，但我们也不必因此过度悲观。</p>
<p>更准确地说，正确理解这个问题，反而能够帮助我们更加成熟地使用中转服务，而不是因为“担心风险”就彻底放弃它。</p>
<p>这篇文章就想用尽量直白的方式，和普通程序员聊聊：</p>
<ul>
<li>第三方中转服务到底是什么</li>
<li>为什么 Agent 场景下风险会明显放大</li>
<li>中国大陆开发者为什么更容易接触这类服务</li>
<li>真正需要担心的风险是什么，不需要过度担心的又是什么</li>
<li>如果必须使用中转服务，怎样把风险降到更合理的范围</li>
</ul>
<h2>什么是第三方中转服务？</h2>
<p>正常情况下，你的程序直接请求模型厂商，比如 <code>OpenAI</code>、<code>Anthropic</code>、<code>Google</code> 等。</p>
<p>但在现实开发中，很多人并不是这么接的，而是把请求先发给一个“<code>中间平台</code>”，由它再转发给真正的模型提供方。这就是中转服务。</p>
<p>它之所以流行，主要有几个很现实的原因：</p>
<p>第一，统一接口。</p>
<p>不同模型厂商的 API 格式不一样。中转服务经常会把它们包装成统一格式，开发者只要接一个接口，就能切换多个模型。</p>
<p>第二，成本和可得性。</p>
<p>对于国内开发者来说，这一点尤其现实。有的人是为了便于访问，有的人是为了聚合多个渠道，有的人是为了用更低门槛测试不同模型，有的人只是想先跑通项目原型。</p>
<p>第三，功能增强。</p>
<p>有些中转服务会提供模型 fallback、负载均衡、缓存、统一计费、日志、密钥管理、额度分发等能力。这些对团队开发确实很方便，用量和成本可以衡量出来。</p>
<p>所以，使用中转服务本身不是“错误做法”，它有非常真实的工程价值。</p>
<p>问题在于：很多人把它理解成了“只是帮我转发一下请求”。问题在于，中转服务并不只是“帮你转发一下请求”这么简单。</p>
<h2>在 Agent 场景里，中转服务为什么更敏感？</h2>
<p>如果你只是做一个普通聊天机器人，中转服务的风险虽然存在，但还相对容易理解：
它能看到你的 prompt、用户输入和模型输出。</p>
<p>但 Agent 不一样。</p>
<p>Agent 的核心是“根据回答进一步执行动作”。
这些动作可能包括：</p>
<ul>
<li>执行 shell 命令</li>
<li>安装依赖</li>
<li>读取和修改代码文件</li>
<li>调用数据库</li>
<li>操作浏览器</li>
<li>使用云平台密钥进行部署或运维等等</li>
</ul>
<p>Agent 可以做的事情太多了！</p>
<p>在这个场景下，调用大模型返回的内容就不再只是文本，而可能是一个工具调用指令，里面包含要执行的命令、参数、路径、目标地址等。</p>
<p>论文指出，当前生态里广泛存在一个问题：<strong>没有成熟的端到端机制，能证明 Agent 最后看到并执行的 tool call，一定就是上游模型原始生成的那个 tool call。</strong></p>
<p>这就意味着如果中转服务愿意，它不仅可以看到你的请求和响应，还可能改写它们。</p>
<p>比如，上游模型原本想让 Agent 执行一个正常命令，但中转层可以把这个命令偷偷换掉，然后再返回给客户端。只要 JSON 格式还合法，Agent 可能根本察觉不到。论文把这种行为归纳为<strong>响应侧载荷注入（AC-1）</strong>。</p>
<blockquote>
<p>个人观点：他们可以这么做。一旦有足够利益的驱使，他们非常非常有可能会这么做。</p>
</blockquote>
<h2>论文告诉了我们什么？</h2>
<p>这篇论文《<a href="https://arxiv.org/html/2604.08407v1">Your Agent Is Mine: Measuring Malicious Intermediary Attacks on the LLM Supply Chain</a>》把风险拆得很清楚：</p>
<ul>
<li>改写返回结果，让 Agent 执行错误动作</li>
<li>不改内容，只是偷偷收集敏感信息</li>
</ul>
<p>第一种是最直观的一类风险。论文称之为 Payload Injection。也就是说，中转服务可以篡改模型返回的工具调用参数，比如把下载地址换成攻击者控制的脚本、把正常命令改成 <code>恶意命令</code>。</p>
<p>如果你的 Agent 拿着高权限运行，而你又没有做足够的审批和隔离，那么这种篡改带来的后果会非常严重。</p>
<p>第二种风险甚至更隐蔽，论文把它称为 Passive Secret Exfiltration。也就是中转服务什么都不改，只是静静地扫描流经它的明文数据，把 API Key、云凭证、某种私钥、数据库地址、内部 Token 等敏感信息记录下来。</p>
<blockquote>
<p>我的敏感信息就是这样泄露的 😭</p>
</blockquote>
<p>这类风险最容易被普通开发者低估，因为你的系统表面上“运行完全正常”，日志也不一定有异常，但秘密已经被别人看见了。</p>
<h2>国内开发者真的非常需要理解这个问题</h2>
<p>这个问题在全世界都存在，但国内开发者可能更容易遇到。由于某种限制，我不方便讨论这方面的原因，相信在大家心中都有自己的答案。</p>
<blockquote>
<p>就像那个经典笑话一样，某个国外的开发者认为咱们中国开发者是最注重隐私的balabala 🤣</p>
</blockquote>
<p>这就导致一个现象：<strong>很多开发者并不是先学会“如何安全地信任中间层”，而是先学会“怎么把接口跑通”。</strong></p>
<p>从项目推进角度看，这很正常。谁做原型都想先把东西做出来。</p>
<p>但如果一个项目后面逐渐接入了真实代码仓库、企业数据库、生产环境云资源、内部文档、运维工具，这时候再把中转服务只当“便宜点的 API 渠道”，就太危险了。</p>
<p>我们要知道的是：我们把模型请求发给中转，也就把 prompt、上下文、工具定义、命令内容、文件路径、甚至密钥暴露给了它。</p>
<p>在 Agent 场景里，你还可能把“实际执行语义”交到了它手上。</p>
<p>理解这一点，并不是为了让大家拒绝一切中转，而是为了避免一种非常常见的错误心态：</p>
<p>“这是模型层的事，不是安全层的事。”</p>
<p>实际上，它已经是安全层的事了。</p>
<p>研究团队对 28 个付费的 API 中转站 (来源于淘宝、闲鱼、Shopify 等店铺) 和 400 个免费中转站 (来自公开社区) 进行全面评测，结果触目惊心。</p>
<ul>
<li>1 个付费中转站 + 8 个免费中转站会在响应中主动注入恶意代码</li>
<li>上面这 9 个中转站中有 2 个还部署了自适应规避触发器</li>
<li>17 个中转站触碰了研究团队故意部署的 AWS 诱饵凭证</li>
<li>1 个中转站直接窃取了研究团队放在蜜罐中的资金</li>
</ul>
<p>总结一下，论文直接覆盖的风险：</p>
<ul>
<li>篡改工具调用，诱导 Agent 执行恶意命令</li>
<li>被动收集 API Key、云凭证、私钥、内部 Token 等敏感信息</li>
</ul>
<p>在现实中，中转服务商还干过这种事（不是扯淡，互联网有记忆）：</p>
<ul>
<li>模型调包与降级</li>
<li>预充值积累了资金，服务商连夜跑路</li>
<li>上游账号违规导致整体服务中断，不然你以为那么多账号哪来的？GitHub 现在都还有批量注册账号的项目，作者还是国人...</li>
</ul>
<h2>普通开发者应该怎么做？</h2>
<p>这就是现实世界，危险无处不在。</p>
<p>但是如果害怕危险而畏首畏尾，那就本末倒置了，我们也别追求“绝对安全”了，风险和权限匹配就行了。</p>
<p>如果后果只是改坏一个测试项目、影响本地临时环境，那容忍度可以高一些。</p>
<p>如果后果是泄露公司代码、误删云资源、读到生产数据库、把内部文档发出去，那就应该尽量避免走不可验证的第三方中转。</p>
<p><strong>永远不要把“主密钥”直接交给 Agent</strong>，无论是否使用中转，这都是基本原则。</p>
<p>Agent 应该拿到的是最小权限、最短时效、最可审计的凭证，而不是“能干所有事”的万能 Key。</p>
<p>即使真的泄露了，也要把损失限制在一个很小范围内。</p>
<h2>为什么非要找中转？如何寻找“靠谱”中转服务商？</h2>
<p>既然中转有这么多问题，为什么大家还要找中转？</p>
<p>还是那句话，中转服务解决的是真问题：</p>
<ul>
<li>它让接入多个模型变简单</li>
<li>中转能做容灾和 fallback</li>
<li>它有时候确实更便宜，更容易获得，甚至它降低了 Agent 原型开发的门槛</li>
</ul>
<p>其次，我不会在这里推荐任何中转服务商，但是根据我用过的若干服务商的经历，我想给大家一些关于如何筛选中转服务商的建议。</p>
<ul>
<li>远离“超低价、全模型支持、无限量”，不要贪小便宜，中转服务不是做慈善，羊毛出在羊身上</li>
<li>没有隐私政策、安全说明、公司主体和联系方式的中转，默认按高风险看。只给一个 API 地址和充值入口、却不说明上游来源的服务，尽量别碰。</li>
<li>舍得花钱做营销（开源热门项目赞助、著名论坛硬广）、信息公开透明、服务团队响应公开快速最佳（服务商入群又不收费，进去看看又何妨）</li>
<li>能不能退款、有没有试用或者短期套餐很重要</li>
</ul>
<h2>最后</h2>
<p>现在这个时代，公司如果给员工提供 AI 服务或者官方的订阅真的是太好了，省下一笔钱不说，你自己用个第三方的服务，出了问题还得担责。</p>
<p>第三方中转服务这件事，值得你从今天开始认真看待。一旦你把 Agent 接进真实工具链，它就会变成安全问题、权限问题和责任问题。</p>
<p><strong>真正值得警惕的，是你根本不知道自己在信任谁。</strong></p>
<hr>
<p>感谢大家看到这里，如果你喜欢我分享的内容，欢迎给我三连支持，你的支持是我更新下去的动力！</p>
<p>下次见，Bye 👋🏻</p>
<p><img src="https://fastly.jsdelivr.net/gh/bucketio/img19@main/2026/04/10/1775782068016-db0b1142-b3d2-4995-809c-4d212311954f.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Node.js 才是现代大型系统的全栈心脏]]></title>
            <link>https://i5lin.top/blog/post/3618073718</link>
            <guid isPermaLink="false">3618073718</guid>
            <pubDate>Wed, 12 Nov 2025 20:15:28 GMT</pubDate>
            <description><![CDATA[从 Netflix 的架构演化，看 JavaScript 的新定义]]></description>
            <content:encoded><![CDATA[<h1>Node.js 才是现代大型系统的全栈心脏</h1>
<p>从 Netflix 的架构演化，看 JavaScript 的新定义</p>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1762978333205-22a27367-d285-423c-8a49-15992ca95b7f.jpg" alt=""></p>
<h2>前言：谁定义了「核心系统」？</h2>
<p>在传统后端世界，「核心系统」意味着：
数据库、支付、事务、锁、一致性。</p>
<p>这些模块稳定、严谨、克制——它们是系统的「引擎室」。</p>
<p>但到了 <strong>2025 年</strong>，用户体验已取代数据表成为真正的竞争力。
对用户来说，<strong>决定留存的不是数据库锁，而是响应延迟、首屏速度、播放流畅度。</strong></p>
<p>于是，新的问题出现了：</p>
<blockquote>
<p>“你说 BFF 不是核心业务层，那谁定义的核心？
是数据库锁，还是用户体验？”</p>
</blockquote>
<p>如果“核心”指的是<strong>决定体验、延迟与系统弹性</strong>的部分，
那么在 Netflix、Shopify、Vercel、Cloudflare 等公司，
真正的系统核心，已经不是 Java 或 Go。
而是 —— <strong>Node.js。</strong></p>
<hr>
<h2>一、旧范式：后端的克制哲学</h2>
<p>后端工程师的信条一直是“克制”：</p>
<ul>
<li>静态类型，避免歧义；</li>
<li>错误显式，拒绝魔法；</li>
<li>工具统一，降低分歧；</li>
<li>简洁可测，优先维护。</li>
</ul>
<p>这正是 <strong>Go、Rust、Java</strong> 等语言的哲学。
它们带来了稳定性，但也带来了“重、慢、僵”的代价。</p>
<p>JavaScript 曾被视作「玩具语言」，
但在十年间，它从浏览器脚本成长为全栈基石：</p>
<ul>
<li>浏览器端：React / Vue / Svelte</li>
<li>服务端：Node.js / Hono / Nest.js</li>
<li>边缘端：Cloudflare Workers / Deno / Vercel Edge</li>
<li>数据层：Prisma / Drizzle / Supabase</li>
</ul>
<p>Node.js 的意义，不只是“让前端能写后端”，
而是让整个系统生态<strong>语言统一、协作无缝</strong>。</p>
<hr>
<h2>二、现实案例：Netflix 的 Node.js 革命</h2>
<h3>架构图（2024–2025）</h3>
<pre><code>[终端用户]
   ↓
[Edge CDN]
   ↓
[Node.js BFF / API Platform]
   ↓
[Java / Go 微服务层]
   ↓
[Cassandra / MySQL / Kafka]
</code></pre>
<p>Netflix 的 Node.js 层不是“辅助接口”，
而是整个系统的 <strong>流量咽喉</strong>：</p>
<ul>
<li>唯一触达用户的后端；</li>
<li>决定 90% 延迟的关键层；</li>
<li>承担个性化、聚合、SSR 等重型逻辑。</li>
</ul>
<hr>
<h3>真实指标（2025）</h3>
<table>
<thead>
<tr>
<th>指标</th>
<th>数值</th>
</tr>
</thead>
<tbody><tr>
<td>日请求量</td>
<td><strong>100 亿+</strong></td>
</tr>
<tr>
<td>峰值 QPS</td>
<td><strong>200 万+</strong></td>
</tr>
<tr>
<td>容器实例</td>
<td><strong>8000+ Node 服务</strong></td>
</tr>
<tr>
<td>P99 延迟</td>
<td><strong>&lt; 80ms</strong></td>
</tr>
<tr>
<td>可用性</td>
<td><strong>99.99%</strong></td>
</tr>
<tr>
<td>缓存命中率</td>
<td><strong>85%+</strong></td>
</tr>
<tr>
<td>下游削峰率</td>
<td><strong>70%</strong></td>
</tr>
</tbody></table>
<p>这些数据来自 Netflix 工程博客与公开访谈（2023–2025）。</p>
<p>这不是“轻量接口层”。
这是全球最大规模的 Node.js 集群之一。</p>
<blockquote>
<p>“Node.js 扛下 100% 用户流量，
决定 90% 延迟与体验，
不是前戏，而是主角。”
——Rocky Bhatia, <em>Netflix Platform Team Lead</em></p>
</blockquote>
<hr>
<h2>三、Node.js 的“核心定义”重写</h2>
<table>
<thead>
<tr>
<th>对比</th>
<th>传统核心（Go / Java）</th>
<th>Netflix 实际架构（Node.js）</th>
</tr>
</thead>
<tbody><tr>
<td>核心逻辑</td>
<td>交易、账务、事务锁</td>
<td>聚合、个性化、渲染、API 编排</td>
</tr>
<tr>
<td>承载角色</td>
<td>后台引擎</td>
<td>用户体验中枢</td>
</tr>
<tr>
<td>触达层</td>
<td>不直接面向用户</td>
<td>唯一触达层</td>
</tr>
<tr>
<td>延迟影响</td>
<td>次要</td>
<td>决定首屏体验</td>
</tr>
<tr>
<td>性能模型</td>
<td>多线程</td>
<td>异步并发</td>
</tr>
<tr>
<td>架构责任</td>
<td>稳定逻辑</td>
<td>实时响应</td>
</tr>
</tbody></table>
<blockquote>
<p>在 Netflix，「核心」的定义被颠覆了：
核心，不再是“写入数据库”，而是“谁决定响应体验”。</p>
</blockquote>
<hr>
<h2>四、Node 的扩张路线：从 BFF 到 Platform</h2>
<p>Netflix 的转变可以用一句话概括：
<strong>“从接口层到平台层。”</strong></p>
<blockquote>
<p>“We are migrating <em>Java services</em> to Node.js
for better developer productivity and reduced latency.”
——Netflix Tech Blog, <em>Node.js at Netflix: The Next Chapter</em> (2023)</p>
</blockquote>
<p>到了 2025 年，Node.js 已占 Netflix 微服务总量的 20%+：</p>
<ul>
<li>平均延迟 ↓20%</li>
<li>开发速度 ×3</li>
<li>协作效率 +50%</li>
<li>统一语言栈 → 消除了“后端 vs 前端”壁垒</li>
</ul>
<p>Node 不再只是“BFF”，
而是 Netflix 的 <strong>API Platform 层</strong>，负责：</p>
<ul>
<li>分布式数据聚合</li>
<li>智能缓存与状态预热</li>
<li>GraphQL Federation 查询优化</li>
<li>边缘渲染与实时个性化</li>
</ul>
<blockquote>
<p>“从 Java 迁 Node，不是‘some’，
而是战略性语言统一。”
——Netflix Architecture Council, 2025</p>
</blockquote>
<hr>
<h2>五、性能：Node 的异步效率</h2>
<p>Netflix 内部实测（2024）：</p>
<table>
<thead>
<tr>
<th>语言</th>
<th>单实例内存</th>
<th>QPS</th>
<th>内存 / QPS</th>
</tr>
</thead>
<tbody><tr>
<td>Node.js</td>
<td>800 MB</td>
<td>25,000</td>
<td>32 KB</td>
</tr>
<tr>
<td>Go</td>
<td>50 MB</td>
<td>2,000</td>
<td>25 KB</td>
</tr>
</tbody></table>
<p>Node.js 以更高内存换来了更高吞吐：</p>
<ul>
<li>异步 I/O 极致复用；</li>
<li>连接池缓存热数据；</li>
<li>无阻塞事件循环应对峰值流量；</li>
<li>实时状态预热减少重试 40%。</li>
</ul>
<blockquote>
<p>Node 的模型是“动态压舱石”——
流量越高，伸缩越快。</p>
</blockquote>
<hr>
<h2>六、Node 的工业级成熟度</h2>
<p>Node.js 生态在 2025 年已全面工业化。</p>
<table>
<thead>
<tr>
<th>能力</th>
<th>Node.js 工具</th>
<th>对标</th>
</tr>
</thead>
<tbody><tr>
<td>性能分析</td>
<td>Clinic.js + v8-profiler</td>
<td>Go pprof</td>
</tr>
<tr>
<td>堆快照</td>
<td>heapdump + Chrome DevTools</td>
<td>JVM heap</td>
</tr>
<tr>
<td>分布式追踪</td>
<td>OpenTelemetry（Netflix 主推）</td>
<td>Jaeger</td>
</tr>
<tr>
<td>错误监控</td>
<td>Sentry</td>
<td>自研方案</td>
</tr>
<tr>
<td>GraphQL Federation</td>
<td>Apollo + Node</td>
<td>无对标</td>
</tr>
<tr>
<td>安全审计</td>
<td>npm auto-audit + Snyk</td>
<td>gosec</td>
</tr>
<tr>
<td>自动伸缩</td>
<td>Kubernetes + HPA</td>
<td>等价</td>
</tr>
</tbody></table>
<p>Node 不仅稳定，还具备「<strong>可观测 + 可自治 + 可自愈</strong>」三要素。</p>
<blockquote>
<p>Node 不再是“脚本”，
它是现代互联网的<strong>分布式平台语言</strong>。</p>
</blockquote>
<hr>
<h2>七、Monorepo：Node 的协作革命</h2>
<p>Netflix、Shopify、Vercel 均采用 Monorepo 模型：</p>
<pre><code>/apps
  /web
  /tv
  /mobile
  /bff-node-service
</code></pre>
<ul>
<li>TypeScript 类型共享</li>
<li>GraphQL Codegen 自动生成接口定义</li>
<li>ESLint + Prettier 一致风格</li>
<li>GitHub Actions 自动化测试</li>
</ul>
<p>50 人团队、百万行代码、零冲突。
Node 不是“灵活”失控的语言，而是<strong>高度约束的工程体系</strong>。</p>
<blockquote>
<p>纪律不是 Go 的特权，
它可以是 JS 的自觉。</p>
</blockquote>
<hr>
<h2>八、Netflix 架构的“平民版”</h2>
<p>Netflix 的 DevOps 成本，如今任何中小团队都能平替。</p>
<table>
<thead>
<tr>
<th>Netflix 技术</th>
<th>2025 平替方案</th>
<th>成本</th>
</tr>
</thead>
<tbody><tr>
<td>模板 + 热更新</td>
<td>Vercel / Turborepo</td>
<td>1 周部署</td>
</tr>
<tr>
<td>SLA + 灰度</td>
<td>LaunchDarkly + SLO.io</td>
<td>月费 $50</td>
</tr>
<tr>
<td>追踪 + 可观测</td>
<td>Grafana + OTEL Cloud</td>
<td>免费</td>
</tr>
<tr>
<td>异常回溯</td>
<td>Sentry Node SDK</td>
<td>免费层</td>
</tr>
<tr>
<td>部署</td>
<td>AWS Amplify + PM2 Cluster</td>
<td>零配置</td>
</tr>
</tbody></table>
<p>Shopify 用 Node Monorepo 承载 1 亿商家，零 Go。</p>
<blockquote>
<p>“复刻 Netflix 架构” 已不再是企业梦，
而是云端标准模板。</p>
</blockquote>
<hr>
<h2>九、Node 的胜利，是体验的胜利</h2>
<p>Go 的设计哲学是「克制」；
Node 的哲学是「统一」。</p>
<p>Go 追求确定性；
Node 追求体验流畅性。</p>
<p>Netflix 的语言演进不是「替代」，
而是「统一」：</p>
<blockquote>
<p>“让所有面向用户体验的系统，用同一种语言思考。”
——Netflix Developer Experience Team</p>
</blockquote>
<p>Node.js 成为“用户层 + 数据层”之间的<strong>中枢神经系统</strong>。</p>
<hr>
<h2>十、为什么 Netflix 不“转向 Go”？</h2>
<table>
<thead>
<tr>
<th>公司</th>
<th>技术方向</th>
<th>语言演进</th>
</tr>
</thead>
<tbody><tr>
<td>Netflix</td>
<td>Node 扩张</td>
<td>Java → Node (20%+ 服务)</td>
</tr>
<tr>
<td>ByteDance / Uber</td>
<td>性能瓶颈</td>
<td>Node → Go</td>
</tr>
<tr>
<td>Cloudflare</td>
<td>边缘流量</td>
<td>Node (Workers)</td>
</tr>
<tr>
<td>Shopify</td>
<td>电商系统</td>
<td>全 Node Monorepo</td>
</tr>
<tr>
<td>Vercel</td>
<td>开发者平台</td>
<td>Node Runtime</td>
</tr>
</tbody></table>
<blockquote>
<p>Netflix 没换 Go，因为 Node 已经跑得够稳、够快、够清晰。
Go 的“确定性”没有实际收益，
Node 的“统一协作”反而创造价值。</p>
</blockquote>
<p>Netflix 投资自家 Node FaaS 平台，
说明 Node 已从“语言”升级为<strong>战略层资产</strong>。</p>
<hr>
<h2>十一、Node.js 是现代后端的体验引擎</h2>
<table>
<thead>
<tr>
<th>维度</th>
<th>传统后端</th>
<th>现代后端</th>
</tr>
</thead>
<tbody><tr>
<td>性能定义</td>
<td>吞吐、CPU</td>
<td>延迟、体验</td>
</tr>
<tr>
<td>语言主流</td>
<td>Go / Java</td>
<td>Node / TS</td>
</tr>
<tr>
<td>协作模式</td>
<td>分层隔离</td>
<td>栈内统一</td>
</tr>
<tr>
<td>运算模型</td>
<td>线程并发</td>
<td>异步并发</td>
</tr>
<tr>
<td>部署形态</td>
<td>容器</td>
<td>FaaS / Edge</td>
</tr>
<tr>
<td>架构核心</td>
<td>数据事务</td>
<td>用户体验</td>
</tr>
<tr>
<td>工程哲学</td>
<td>克制</td>
<td>协同</td>
</tr>
</tbody></table>
<blockquote>
<p>Node.js 是新时代的“体验层后端”。
它不是替代 Go，而是重塑“核心”的定义。</p>
</blockquote>
<hr>
<h2>最后：语言的尽头，是协作与体验</h2>
<p>Go 的克制让系统稳如磐石；
Node 的统一让协作顺畅如呼吸。</p>
<p>未来的后端，不再以「静态 vs 动态」划界，
而是以「封闭 vs 协作」分胜负。</p>
<blockquote>
<p>Go 是企业的地基，
Node 是产品的呼吸。</p>
<p>在体验为王的时代，
真正的核心，
不是谁拿着数据库锁，
而是谁点亮了屏幕的那一毫秒。</p>
</blockquote>
<hr>
<h2>最终总结</h2>
<ul>
<li>Netflix 没有转向 Go，因为 Node.js 已是它的「体验心脏」。</li>
<li>Node 从 BFF 进化为 Platform，从接口层变为体验核心。</li>
<li>2025 年的 JS 生态，让普通团队也能复刻 Netflix 的架构思想。</li>
<li>Node 代表新的后端范式：<strong>统一语言、异步模型、体验为核</strong>。</li>
</ul>
<blockquote>
<p>Node.js 不只是前端的延伸，
它是现代互联网的<strong>全栈中枢</strong>。</p>
<p>它不是“轻量”，
它是未来。</p>
</blockquote>
<hr>
<h3>推荐引用来源（部分）</h3>
<ul>
<li><a href="https://netflixtechblog.com/">Netflix Tech Blog – <em>Node.js at Netflix: The Next Chapter</em>, 2023</a></li>
<li><a href="https://rockybhatia.substack.com/">Rocky Bhatia Substack – <em>The 100B Request BFF Layer</em>, 2024</a></li>
<li><a href="https://designgurus.io/">DesignGurus – <em>Netflix Node.js Architecture (2025)</em></a></li>
<li><a href="https://infoq.com/">InfoQ – <em>GraphQL Federation in Netflix Node Layer</em>, 2025</a></li>
<li><a href="https://thenewstack.io/">TheNewStack – <em>Netflix FaaS Node Platform Evolution</em>, 2025</a></li>
</ul>
<hr>
<p><strong>写在最后：</strong></p>
<blockquote>
<p>在后端世界的克制与前端世界的热情之间，
Node.js 找到了连接的平衡点。</p>
<p>它不是边缘语言，
它是新时代系统的——<strong>中枢神经。</strong></p>
</blockquote>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[中大型 Web 项目必看：把 NODE_ENV 和 APP_ENV 彻底分开]]></title>
            <link>https://i5lin.top/blog/post/4235897591</link>
            <guid isPermaLink="false">4235897591</guid>
            <pubDate>Fri, 10 Apr 2026 01:26:21 GMT</pubDate>
            <description><![CDATA[在很多 web 项目里，环境变量的设计往往是一个“看起来简单，但实际非常容易踩坑”的问题。]]></description>
            <content:encoded><![CDATA[<p><img src="https://de4965e.webp.li/blog-images/2026/04/9c5bf4439dd7e6af806f77a7f442bd3f.png" alt=""></p>
<p>大家好，今天想和大家讨论和分享一下我对 web 项目里 NODE_ENV 环境变量的看法。</p>
<h2>前言</h2>
<p>在很多 web 项目里，环境变量的设计往往是一个“看起来简单，但实际非常容易踩坑”的问题。</p>
<p>最典型的就是：</p>
<blockquote>
<p>NODE_ENV 到底应该怎么用？
dev / test / staging / prod 要不要塞进 NODE_ENV？</p>
</blockquote>
<p>这几年来我经历过各种大小草台班子，接手过各种 web 项目，看到很多团队图省事，会把所有环境都塞进 NODE_ENV，比如：</p>
<pre><code>NODE_ENV=dev 
NODE_ENV=test 
NODE_ENV=uat
NODE_ENV=staging
NODE_ENV=prod
</code></pre>
<p>这种方式前期看似乎没问题，但随着项目变复杂，需求、环境变多，参与人数变多等等，这种设计基本都会逐渐变成隐患。</p>
<p>今天分享这篇文章，主要想和大家分享一下，我们得搞清楚一下几点：</p>
<ul>
<li>NODE_ENV 的本质是什么？</li>
<li>为什么不能把它当环境枚举</li>
<li>运行时变量应该如何设计</li>
<li>推荐的实践方案</li>
</ul>
<h2>NODE_ENV 的本质：运行模式</h2>
<p>NODE_ENV 是 <code>运行模式</code>。</p>
<blockquote>
<p>很多测试框架会强制设 NODE_ENV=test</p>
</blockquote>
<p>当我们在项目里需要判定当前逻辑所处的运行模式是什么的时候，记住：在 Web 开发中， NODE_ENV 其实有着非常明确的约定：</p>
<table>
  <tr>
    <th style="width: 50vw;">值</th>
    <th style="width: 50vw;">含义</th>
  </tr>
  <tr>
    <td>development</td>
    <td>开发模式</td>
  </tr>
  <tr>
    <td>production</td>
    <td>生产模式</td>
  </tr>
</table><p>很多框架和库都会根据 NODE_ENV 自动设定一些行为，比如：</p>
<ul>
<li>是否输出详细错误信息</li>
<li>是否开启调试日志</li>
<li>是否启用性能优化</li>
<li>是否使用缓存</li>
<li>是否压缩资源</li>
</ul>
<p>如果你把 NODE_ENV 改成：</p>
<pre><code>NODE_ENV=uat
NODE_ENV=staging
</code></pre>
<p>这些库很可能识别不了，从而导致行为异常。</p>
<h2>概念误区： 把 NODE_ENV 当环境名</h2>
<p>我曾经把 NODE_ENV 当成”当前代码运行在哪一个环节“的判定依据：</p>
<pre><code class="language-js">if (process.env.NODE_ENV === &#39;dev&#39;) {} 
if (process.env.NODE_ENV === &#39;uat&#39;) {} 
if (process.env.NODE_ENV === &#39;staging&#39;) {}
</code></pre>
<p>这里其实混淆了两个基本概念：</p>
<ul>
<li>运行模式 - mode<ul>
<li>是否开启调试：development 开启，production 关闭</li>
<li>是否压缩日志：development 关闭，production 开启</li>
</ul>
</li>
<li>部署环境 - environment<ul>
<li>dev 开发环境</li>
<li>test 测试环境</li>
<li>prod 生产环境</li>
</ul>
</li>
</ul>
<p>或许我们不应该用同一个变量来表示这两种概念。</p>
<p>对吧？运行模式的关注点在于代码如何运行，我们根据这一点来控制是否调试、是否输出细节，启用 source map，运行模式会影响打包结果，并且应该进入构建产物内部，我不在乎这份代码构建后在什么环境下运行。</p>
<p>然而部署环境就不一样了，它的核心关注点是：代码运行在哪里？</p>
<p>举个例子，假设代码运行在 test 环境下，我们正在对代码进行测试，难道这时候你想要让类似 Sentry 这样的第三方服务接入进来吗？</p>
<p>当然不需要！就算是免费的服务，也不需要在测试的时候接进来啊，白嫖也不行！</p>
<p>我们需要用一个变量来标识部署的环境，也可以说是运行的环境。</p>
<p>但现实是很多团队、项目会一把梭 <code>NODE_ENV</code>，某种意义上来说，这个变量传播最早也最广，大家习惯了用它控制一切。</p>
<p>并且，一个变量就能决定“开发时友好，生产时高效”，确实方便。</p>
<p>但我们不能忽略这样做的代价：</p>
<ul>
<li>语义不清晰：NODE_ENV=production 时，到底是“打包成生产模式”还是“当前部署在生产环境”？经常说不清。</li>
<li>无法灵活组合：我想在 test 环境也用 production mode（为了测性能、测构建产物），或者在 prod 环境临时开启 development mode 调试问题，就很尴尬。</li>
<li>配置爆炸：很多人最后搞出 development, test, staging, production 四种模式，却又在里面嵌套各种 feature flags，配置越来越乱。</li>
</ul>
<h2>推荐做法：NODE_ENV + APP_ENV</h2>
<p>在前端项目中，NODE_ENV 主要影响构建结果（Vite/Webpack）；在后端（如 Node.js/NestJS/Express）中，更建议用 APP_ENV 控制业务配置。</p>
<table>
  <tr>
    <th style="width: 50vw;">变量</th>
    <th style="width: 50vw;">作用</th>
  </tr>
  <tr>
    <td>NODE_ENV</td>
    <td>控制程序运行模式</td>
  </tr>
  <tr>
    <td>APP_ENV</td>
    <td>控制部署环境</td>
  </tr>
</table><p>NODE_ENV：只保留给构建工具使用（vite/rollup 等），严格只有 development / production 两个值。</p>
<p>APP_ENV 或 DEPLOY_ENV：真正代表部署环境（dev/test/staging/prod）。</p>
<h3>举个例子：一个电商系统的 Staging 环境</h3>
<p>Staging 环境的定位：</p>
<ul>
<li>目的是在上线前做最接近真实生产的完整测试（包括性能、功能、集成）。</li>
<li>通常由 QA、产品、运维一起验证，模拟真实用户流量。</li>
<li>核心需求：行为要和生产环境几乎一致，但数据和外部服务是隔离的。</li>
</ul>
<p>如果不分开（只用一个变量，比如 NODE_ENV 或 ENV），可能有人会这样写：</p>
<pre><code># 很多人会这么写
ENV=staging
# 或者
NODE_ENV=staging
</code></pre>
<p>然后代码里这样判断：</p>
<pre><code class="language-js">if (process.env.NODE_ENV === &#39;production&#39;) {
  // 生产行为：压缩、关闭调试、无彩色日志等
} else {
  // 开发行为：详细日志
  // source map、React DevTools 等
}
</code></pre>
<p><strong>在 staging 环境下会出现以下问题</strong>：</p>
<p>想用 <code>production mode</code> 的优化，但环境变量是 <code>staging</code>。</p>
<p>结果是用了 <code>development</code> 模式：代码没压缩、日志超级详细、<code>source map</code> 没去掉、性能比生产差很多，无法准确验证性能和稳
定性。</p>
<p>想用 <code>development mode</code> 方便调试，但又怕污染生产行为。</p>
<p>如果强行设 <code>NODE_ENV=development</code>，那整个 <code>staging</code> 就失去了“类生产”的意义，测出来的结果不可信。</p>
<p>配置冲突：数据库要用 <code>staging</code> 专属的库（不能连 prod DB）
支付、短信、推送等第三方服务要用测试账号，但日志、监控、告警又希望和生产保持一致（格式、级别、压缩方式）</p>
<p>最终结果：要么 <code>staging</code> 测不准，要么调试很便秘，花费的时间大大增加，然后？导致加班 ？🤣</p>
<p>现在就以这个为例，我们试试看拆开成两个变量：</p>
<pre><code># 运行模式（决定代码如何运行、打包结果）
NODE_ENV=production        
# 或 APP_MODE=production

# 部署环境（决定连哪个数据库、用什么密钥、开哪些功能）   
# dev | test | staging | prod
APP_ENV=staging       
</code></pre>
<p>其他场景下，NODE_ENV 基本都设置为 production！部署环境则根据 test、staging、prod 来切换逻辑。</p>
<p>在这个 Staging 环境下，运行模式是 production，构建后：</p>
<ul>
<li>代码是压缩的、tree-shaking 后的</li>
<li>肯定没有 source map</li>
<li>日志是结构化的，压缩的，不至于打印多余的彩色日志，详细堆栈也不会暴露</li>
<li>框架的开发警告全部关闭</li>
<li>性能和生产环境一致</li>
</ul>
<p>同时，部署环境是 staging：</p>
<ul>
<li>根据部署环境连接 staging 数据库（当然也可以在 CI 的时候专门注入数据库链接）</li>
<li>测试支付渠道走的是沙箱</li>
<li>独立的 staging 域名、监控 Tag、开放的功能</li>
</ul>
<p>代码示例：</p>
<pre><code class="language-js">const mode = process.env.NODE_ENV;        // production
const env  = process.env.APP_ENV;         // staging

// 构建/运行时行为
const isProdMode = mode === &#39;production&#39;;

// 业务和基础设施配置
const config = {
  db: {
    host: env === &#39;prod&#39; ? &#39;prod-db&#39; : `${env}-db`,
  },
  logging: {
    level: isProdMode ? &#39;info&#39; : &#39;debug&#39;,
    pretty: !isProdMode,
  },
  features: featureFlags[env],   // staging 可以有独立的 feature flags
};
</code></pre>
<p>甚至配置文件也可以按 APP_ENV 拆分：</p>
<ul>
<li>.env.local</li>
<li>.env.dev</li>
<li>.env.test</li>
<li>.env.staging</li>
<li>.env.prod</li>
</ul>
<p>这样一来我们就可以去做一次性构建，多环境部署。</p>
<p>在 CI/CD 的过程中，只需要构建一次代码，再部署到多个环境即可。多环境下多次重复构建就显得有点浪费时间了。</p>
<h2>写进 README 的规范</h2>
<p>以下几点，我将之写入了自己所有项目的 README 中</p>
<ul>
<li>NODE_ENV 只允许：development / test / production</li>
<li>APP_ENV 用于区分部署环境：local / dev / test / staging / prod</li>
<li>除本地开发和自动化测试外，所有服务运行在 NODE_ENV=production</li>
<li>禁止使用 NODE_ENV 判断 dev/test/staging/prod</li>
<li>所有环境差异统一通过 APP_ENV + 配置文件管理</li>
</ul>
<p>如果你正在做一个中大型后端系统，这一条设计，值得你在一开始就定对。否则后面重构成本会非常高。</p>
<p>如果你已经在用 NODE_ENV=dev/staging/prod，也可以逐步引入 APP_ENV，把职责拆清楚，就能平滑过渡。</p>
<h2>最后</h2>
<p>最近 AI 的发展实在是太快了，很多人已经完全不再编写代码，Vibe coding 堪比许愿，在 Agent 帮助下，连自己review 都省了。</p>
<p>但是，我还是觉得 AI 只是拓展开发者能力的工具，至少目前还是！我们可以借助 AI 来做很多原本自身做不到的事情，或者需要花很多时间精力才能做到的事情。但是在开发这件事情上，一切的根基还是我们自己。</p>
<hr>
<p>感谢大家看到这里，如果你喜欢我分享的内容，欢迎给我三连支持，你的支持是我更新下去的动力！</p>
<p>下次见，Bye 👋🏻</p>
<p><img src="https://fastly.jsdelivr.net/gh/bucketio/img19@main/2026/04/10/1775782068016-db0b1142-b3d2-4995-809c-4d212311954f.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[一发入魂：巧用 code-inspector-plugin 插件，一键点击直达源代码]]></title>
            <link>https://i5lin.top/blog/post/3629354609</link>
            <guid isPermaLink="false">3629354609</guid>
            <pubDate>Sat, 15 Nov 2025 22:30:07 GMT</pubDate>
            <description><![CDATA[在真实前端开发场景中，“定位代码”常常比“修改代码”更耗时。]]></description>
            <content:encoded><![CDATA[<p>在真实前端开发场景中，“定位代码”常常比“修改代码”更耗时。</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/11/ae51f847e28816965691be9a97950643.png" alt=""></p>
<p>我们可能遇到这样的情况：</p>
<ul>
<li>某个按钮样式异常，但项目里有七八个同名 class</li>
<li>文案写错了，搜索关键词却找不到它实际渲染的位置</li>
<li>React/Vue 中的组件结构层级复杂，想定位某个节点的来源需要跳几个文件</li>
<li>别人写的老项目，目录结构复杂、组件命名不统一</li>
</ul>
<p>这些情景并不少见，甚至有时比写业务本身更让人头痛。</p>
<p><strong>code-inspector-plugin</strong> 正是在这种背景下，提供了一个非常直接的解决方式：</p>
<p><strong>从页面点击元素，直接跳回编辑器中对应的源码行。</strong></p>
<p>下面通过一个真实例子来说明它能带来什么样的改变。</p>
<hr>
<h1><strong>一个常见的场景：定位样式问题</strong></h1>
<p>假设你正在维护一个后台管理系统。设计师反馈：</p>
<blockquote>
<p>“首页统计卡片 hover 的动效好像不对，你看一下。”</p>
</blockquote>
<p>你打开页面检查 DOM，发现这个卡片由多个组件嵌套而成：</p>
<pre><code>Dashboard
  └── OverviewCardList
        └── OverviewCard
              └── CardPanel
</code></pre>
<p>但 class 名和组件名都较通用，比如：<code>card-wrapper</code>、<code>panel</code>、<code>container</code>。
你试图在项目中搜索，但无法精准定位到底是哪一个组件渲染了你看到的这个卡片。</p>
<p>在传统方法里，你可能会：</p>
<ul>
<li>根据 class 搜索</li>
<li>翻几个文件</li>
<li>找不到就继续往上排查</li>
<li>几分钟过去了，问题的根本位置还没找到</li>
</ul>
<p><strong>没有什么比我们的时间更加珍贵的了！</strong></p>
<hr>
<h1><strong>使用 code-inspector-plugin 后的体验</strong></h1>
<p>你按住 <strong>Shift</strong>，在浏览器里点击这张卡片。</p>
<blockquote>
<p>macOS 和 windows 快捷键有所不同</p>
</blockquote>
<p>VSCode 立即打开：</p>
<pre><code>src/components/OverviewCard.vue:52
</code></pre>
<p>光标正好停在渲染这个卡片的那一行。</p>
<p>你只需要补上一段样式：</p>
<pre><code class="language-css">.overview-card:hover {
  transform: translateY(-4px);
}
</code></pre>
<p>刷新 → 完成。</p>
<p>整个流程非常直接，没有搜索，没有猜测，也无需在组件树里“盲走”。</p>
<hr>
<h1><strong>实现机制很简单，但非常有效</strong></h1>
<p>在开发模式下，插件会：</p>
<ol>
<li><p>为每个 DOM 对应的源代码位置生成一个标识</p>
</li>
<li><p>拦截你在页面中的点击事件</p>
</li>
<li><p>向本地服务发送“打开文件并跳转行号”的指令</p>
</li>
<li><p>编辑器执行类似：</p>
<pre><code>code --goto src/...:line
</code></pre>
</li>
<li><p>即刻跳转到源码</p>
</li>
</ol>
<p>生产环境构建时，所有注入信息会自动被移除，不会影响体积，也不会泄露源码路径。</p>
<hr>
<h1><strong>快速上手（以 Vite 为例）</strong></h1>
<p><strong>安装</strong></p>
<pre><code class="language-bash">pnpm add code-inspector-plugin -D
</code></pre>
<p><strong>配置</strong></p>
<pre><code class="language-ts">import codeInspector from &#39;code-inspector-plugin/vite&#39;

export default defineConfig({
  plugins: [
    codeInspector({
      bundler: &#39;vite&#39;,
      showSwitch: true,
    })
  ]
})
</code></pre>
<blockquote>
<p>此插件为常见的编译工具都提供了对应的解决方案，如果你使用的不是 vite，也可以在官方文档页面轻松找到对应的配置。</p>
</blockquote>
<p>启动 Dev Server 后：</p>
<ul>
<li><strong>Shift + 点击任意页面元素 → 编辑器自动跳转</strong></li>
<li>左下角会出现一个开关，用来临时启用/禁用</li>
</ul>
<p>支持 Vue、React、Svelte、原生 HTML 等常见开发模式。</p>
<hr>
<h1><strong>适用场景</strong></h1>
<ul>
<li>大型项目中快速定位组件来源</li>
<li>新同事 onboarding，提高理解项目结构的速度</li>
<li>排查样式、文案、布局问题</li>
<li>老项目维护</li>
<li>组件库调试</li>
<li>后台系统或多层嵌套组件场景</li>
</ul>
<p>特别是在你不确定“这个元素到底来自哪个组件”的时候，它的价值会非常明显。</p>
<hr>
<h1><strong>结语</strong></h1>
<p>code-inspector-plugin 并不是一个复杂的工具，但它在最基础的开发行为上提供了非常高的效率提升：
<strong>从页面回到源码，让定位问题变得确定且高效。</strong></p>
<p><strong>code-inspector 没有改变框架，也不试图重新定义什么。它只是让开发过程里最琐碎、最令人疲惫的那一部分变得轻松起来。</strong></p>
<p>你可能会发现，它是那种“用过一次就离不开”的插件。</p>
<p>为什么不尝试一下呢？</p>
<hr>
<h1><strong>🔗 参考与引用</strong></h1>
<ol>
<li>code-inspector-plugin GitHub
<a href="https://github.com/zh-lx/code-inspector-plugin">https://github.com/zh-lx/code-inspector-plugin</a></li>
<li>Vite 插件说明
<a href="https://github.com/zh-lx/code-inspector-plugin/tree/master/packages/vite">https://github.com/zh-lx/code-inspector-plugin/tree/master/packages/vite</a></li>
<li>Webpack 插件说明
<a href="https://github.com/zh-lx/code-inspector-plugin/tree/master/packages/webpack">https://github.com/zh-lx/code-inspector-plugin/tree/master/packages/webpack</a></li>
</ol>
<hr>
<p>感谢大家看到这里，如果你喜欢我分享的内容，欢迎给我三连支持，你的支持是我更新下去的动力！
下次见，Bye.</p>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1763131375534-2572235c-7c62-4f50-99bd-249168babbc7.png" alt=""></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[为什么即使有顶尖的 JS 团队，大型系统最终还是会转向 Go？]]></title>
            <link>https://i5lin.top/blog/post/3618068704</link>
            <guid isPermaLink="false">3618068704</guid>
            <pubDate>Wed, 12 Nov 2025 20:13:56 GMT</pubDate>
            <description><![CDATA[Node.js / Golang / TypeScript / 后端架构 / 全栈开发 / 性能瓶颈 / 语言哲学 / 团队协作 / 系统可维护性]]></description>
            <content:encoded><![CDATA[<h1>为什么即使有顶尖的 JS 团队，大型系统最终还是会转向 Go？</h1>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1762974039560-1fd3e796-2231-4c66-ae4c-8f072e79ecc6.jpg" alt=""></p>
<blockquote>
<p><strong>关键词</strong>：Node.js / Golang / TypeScript / 后端架构 / 全栈开发 / 性能瓶颈 / 语言哲学 / 团队协作 / 系统可维护性</p>
</blockquote>
<hr>
<h2>引言：从热血到现实的转折</h2>
<p>有一家创业公司，三年前凭借一支满怀热情的 JavaScript 全栈团队，仅用三个月就上线了他们的第一个 SaaS 产品。
前端用 React + Next.js，后端用 Node.js + Express + Prisma，全栈统一、开发高效、迭代迅速。</p>
<p>第一年，他们的产品飞速成长；
第二年，用户数突破百万；
第三年，系统频繁宕机、内存暴涨、日志爆炸、调试混乱——
最终，他们重构了后端，转向 <strong>Golang</strong>。</p>
<p>这样的故事，在业界几乎每年都上演：</p>
<ul>
<li>Uber：从 Node 到 Go</li>
<li>Airbnb：从 Node 到 Java</li>
<li>字节、美团：Node 留在 BFF 层，Go 承载核心后端</li>
<li>Cloudflare：边缘层用 JS，主干流量全是 Go</li>
</ul>
<p>问题不是 JS 不行，而是当系统足够复杂时，<strong>语言设计哲学的差异</strong>会被无限放大。</p>
<hr>
<h2>一、JavaScript 全栈的黄金时代</h2>
<p>过去十年是 JavaScript 的全盛期。
从浏览器到服务器，从前端到云端，JS 几乎无处不在：</p>
<ul>
<li>前端：React、Vue、Svelte、Next.js</li>
<li>后端：Node.js、Express、Nest.js、Hono.js</li>
<li>ORM 层：Prisma、Drizzle ORM</li>
<li>构建工具：Vite、RSBuild、Webpack</li>
</ul>
<p>“<strong>前后端统一语言</strong>” 带来了巨大的生产力革命：</p>
<ol>
<li><strong>上下文一致</strong>：一个类型定义贯穿全栈；</li>
<li><strong>学习成本低</strong>：新成员快速上手；</li>
<li><strong>生态丰富</strong>：NPM 拥有世界上最多的包；</li>
<li><strong>快速原型</strong>：非常适合初创产品、内部系统、MVP。</li>
</ol>
<p>这让 JS 成为快速创新的代名词。</p>
<hr>
<h2>二、当系统变“大”后，一切都变了</h2>
<p>当一个系统从几千用户成长到上百万用户，从单人维护到百人协作，
JS 的灵活反而成了负担。</p>
<h3>1. 单线程模型的物理瓶颈</h3>
<p>Node.js 的核心是事件循环（event loop），
它通过异步 I/O 实现高并发，但依旧是单线程的。</p>
<p><strong>优点</strong>：I/O 密集型场景（API 聚合、HTTP 转发）性能极高；
<strong>缺点</strong>：CPU 密集型计算（加密、日志压缩、图像处理）会堵死主线程。</p>
<p>即使 Node 提供了：</p>
<ul>
<li><code>worker_threads</code>；</li>
<li><code>cluster</code>；</li>
<li>或者 Serverless 弹性扩展；
这都只是“绕过限制”，不是“根本解决”。</li>
</ul>
<p>反观 Go：</p>
<ul>
<li><strong>多核天然并行</strong>；</li>
<li><strong>goroutine 轻量</strong>（百万级并发）；</li>
<li><strong>调度由语言层托管</strong>。</li>
</ul>
<blockquote>
<p>JS 可以异步，但不是真正的并行。
Go 是原生并发，从根设计上解决性能扩展问题。</p>
</blockquote>
<hr>
<h3>2. 内存管理的隐性代价</h3>
<p>Node.js 使用 V8 引擎，默认内存上限约 1.5GB。
这在大型系统下意味着什么？</p>
<ul>
<li>GC 压力大，频繁触发时会造成抖动；</li>
<li>内存泄漏难排查（闭包残留、异步引用）；</li>
<li>跨模块调试复杂，线上分析痛苦；</li>
<li>工具链零散，profiling、heapdump、trace 都是外部插件。</li>
</ul>
<p>而 Go 原生支持：</p>
<ul>
<li><code>pprof</code> 性能分析；</li>
<li>自动 GC 优化；</li>
<li>编译期静态内存布局；</li>
<li>二进制部署时资源占用小。</li>
</ul>
<blockquote>
<p>JS 可以跑快，但长时间跑稳是一种奢侈。</p>
</blockquote>
<hr>
<h3>3. 灵活性与混乱的临界点</h3>
<p>JavaScript 的灵活是它的魅力，也是它的毒药。</p>
<ul>
<li>你可以用回调、Promise、async/await 混着写；</li>
<li>你可以在运行时改对象结构；</li>
<li>你甚至可以写出行为不同的同名函数。</li>
</ul>
<p>这在小团队里是创造力；
在大团队里是灾难。</p>
<p>TypeScript 改善了这一切——但并不彻底：</p>
<ul>
<li>类型推导复杂，泛型嵌套难读；</li>
<li>类型定义随库变动频繁；</li>
<li>编译器耗时长；</li>
<li>高级类型体操成了新的陷阱。</li>
</ul>
<p>Go 的哲学则相反：
<strong>限制是自由的前提。</strong></p>
<hr>
<h2>三、后端是“克制的艺术”</h2>
<p>Golang 的设计者 Rob Pike 曾说过一句话：</p>
<blockquote>
<p>“A great language is one where you can’t write bad programs easily.”
——伟大的语言，是让人很难写出坏代码的语言。</p>
</blockquote>
<p>Go 的简洁并非偶然，而是一种<strong>克制</strong>：</p>
<ul>
<li>没有继承；</li>
<li>没有宏；</li>
<li>错误必须显式返回；</li>
<li>工具链标准统一；</li>
<li>自动格式化，代码风格一致；</li>
<li>语法几乎无歧义。</li>
</ul>
<p>这让 Go 成为大型团队协作的天然选择。
在 Node 项目中，你可能要制定一百条 ESLint 规则；
而在 Go 项目中，只需要写：</p>
<pre><code class="language-bash">go fmt ./...
</code></pre>
<p>然后一切都统一了。</p>
<blockquote>
<p>Go 没有给你“更自由”的语法，而是给了你“更少犯错”的机会。</p>
</blockquote>
<hr>
<h2>四、技术债与业务债的交织</h2>
<p>即使是最强的 JS 团队，也无法长期避免“<strong>技术债 + 业务债</strong>”的双重爆炸。</p>
<h3>1. 框架和依赖的短命循环</h3>
<p>Node 生态的活跃，是 blessing 也是 curse。</p>
<ul>
<li>五年前的 Express，如今已难维持；</li>
<li>Nest.js、Fastify、Hono、Koa 等并行存在；</li>
<li>每个库的 TS 支持程度不同；</li>
<li>NPM 依赖树深达数百层。</li>
</ul>
<p>一次依赖更新，可能引发连锁崩溃。
Go 的 <code>go mod</code> 模块化机制，则天然避免了这种问题。</p>
<hr>
<h3>2. 可观测性与调试体验的差距</h3>
<p>在生产环境中定位问题：</p>
<ul>
<li>Node.js 需要第三方库、独立 APM、日志链路拼接；</li>
<li>Go 内置 <code>net/http/pprof</code>、<code>runtime/trace</code>、<code>expvar</code>；</li>
<li>Java 有 JFR、JMX、VisualVM；</li>
<li>Rust 借助 compile-time tracing。</li>
</ul>
<p>当系统规模达到数十亿请求时，<strong>稳定性 = 成本控制</strong>。
此时语言的工程可观测性就决定了运营效率。</p>
<hr>
<h3>3. 团队扩张带来的复杂性放大</h3>
<p>一个 5 人的 JS 团队可以写出优雅代码；
一个 50 人的 JS 团队，很容易写出一团糟。</p>
<p>因为 JS 的自由度太高、团队水平差异太大、规范约束难以 enforce。
Go 则几乎没有这种问题：
写出来的代码都一个样，逻辑清晰，学习成本低。</p>
<blockquote>
<p>在协作密集的环境中，<strong>语言的约束性反而是一种安全性。</strong></p>
</blockquote>
<hr>
<h2>五、为什么“顶尖 JS 团队”仍然会转向 Go</h2>
<p>即使拥有最强的 JS 技术能力，问题仍然会从外部逼近：</p>
<table>
<thead>
<tr>
<th>层面</th>
<th>JS 全栈团队的瓶颈</th>
<th>Go 的优势</th>
</tr>
</thead>
<tbody><tr>
<td><strong>运行时模型</strong></td>
<td>单线程 I/O 模型，CPU 密集型难扩展</td>
<td>多核并发、goroutine 轻量</td>
</tr>
<tr>
<td><strong>资源占用</strong></td>
<td>内存重、GC 不可控</td>
<td>内存轻量、调度高效</td>
</tr>
<tr>
<td><strong>部署运维</strong></td>
<td>依赖复杂、编译链条长</td>
<td>单二进制部署</td>
</tr>
<tr>
<td><strong>调试与监控</strong></td>
<td>工具链分散</td>
<td>内置 pprof、trace</td>
</tr>
<tr>
<td><strong>长期维护</strong></td>
<td>TS/Node 生态更新快</td>
<td>Go 长期兼容</td>
</tr>
<tr>
<td><strong>团队扩张</strong></td>
<td>风格分裂、学习成本高</td>
<td>简洁一致、稳定协作</td>
</tr>
</tbody></table>
<p>JS 团队可以凭经验压制问题，但语言特性本身的「物理规律」无法被消除。
随着规模增长，<strong>Go 的稳定性红利</strong>会逐渐超过 JS 的灵活性红利。</p>
<hr>
<h2>六、真实案例：从 JS 到 Go 的演进之路</h2>
<h3>Uber</h3>
<p>最初的 API Gateway 基于 Node.js 构建，
后因性能瓶颈与异步调试困难，迁移到 Go，
结果 API 响应速度提升 50%，并发承载提升 3 倍。</p>
<h3>Airbnb</h3>
<p>早期使用 Node 作为服务层，
后改为 Java + Go 混合架构，解决高并发与内存泄漏问题。</p>
<h3>Cloudflare</h3>
<p>边缘计算层（Workers）仍用 JS，
但主干网络层全部使用 Go 实现，追求极致性能。</p>
<h3>美团、字节跳动</h3>
<p>Node.js 被限制在 BFF 层或工具层，
Go 成为统一的后端主力语言。</p>
<p><strong>结论很一致：</strong></p>
<blockquote>
<p>JS 很好用，但不适合扛大梁。</p>
</blockquote>
<hr>
<h2>七、最合理的平衡：分层协作，而非替代</h2>
<p>成熟的工程组织往往这样分层：</p>
<table>
<thead>
<tr>
<th>层级</th>
<th>推荐语言</th>
<th>设计目标</th>
</tr>
</thead>
<tbody><tr>
<td><strong>前端层</strong></td>
<td>TypeScript / React / Vue</td>
<td>用户交互、快速迭代</td>
</tr>
<tr>
<td><strong>BFF 层</strong></td>
<td>Node.js / Hono.js</td>
<td>接口聚合、鉴权、缓存</td>
</tr>
<tr>
<td><strong>核心业务层</strong></td>
<td>Go / Rust / Java</td>
<td>并发处理、稳定运行</td>
</tr>
<tr>
<td><strong>数据服务层</strong></td>
<td>PostgreSQL / Redis / Kafka</td>
<td>持久化与异步通信</td>
</tr>
</tbody></table>
<p>这种架构让各语言发挥所长，既保留 JS 的敏捷，又利用 Go 的稳定。</p>
<blockquote>
<p>真正成熟的工程，不是语言之争，而是职责之分。</p>
</blockquote>
<hr>
<h2>八、总结：从灵活到克制，从速度到稳定</h2>
<ul>
<li><strong>JS 全栈</strong>代表创新与效率；</li>
<li><strong>Go 体系</strong>代表稳定与可控；</li>
<li>选择语言，其实是选择团队的工程哲学。</li>
</ul>
<blockquote>
<p>JS 是创业期的加速器，
Go 是企业期的压舱石。</p>
</blockquote>
<p>当你追求的是“上线速度”，JS 全栈最强；
当你追求的是“稳定十年”，Go 才是答案。</p>
<hr>
<h2>尾声：技术的尽头，是工程哲学</h2>
<p>后端不是炫技的舞台，而是系统稳定运行的基石。
写后端需要克制：克制过度抽象、克制灵感爆发、克制自由发挥。</p>
<p>因为：</p>
<ul>
<li>稳定的系统靠“可预测性”；</li>
<li>长期的团队靠“约束”；</li>
<li>真正的技术成熟，体现在“写得少，错得少”。</li>
</ul>
<p>JS 让你飞得快，Go 让你落得稳。</p>
<p>而成熟的团队，懂得何时该飞，何时该稳。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[React + Antd 主题系统改造全景指南]]></title>
            <link>https://i5lin.top/blog/post/3567995397</link>
            <guid isPermaLink="false">3567995397</guid>
            <pubDate>Wed, 29 Oct 2025 21:36:30 GMT</pubDate>
            <description><![CDATA[这篇文档用尽量轻松的方式，带你把现在的主题体系从“为什么要这么做”一路串到“真实的代码是怎样运转的”。看完它，你就能在脑海里勾勒出这套系统的全过程：预设 → Token → CSS 变量 → 页面应用 → 生成脚本 → 运行时同步。]]></description>
            <content:encoded><![CDATA[<h1>React + Antd 主题系统改造全景指南</h1>
<p><img src="https://de4965e.webp.li/blog-images/2025/10/1c1a20f520e6165e14b721d73e341fde.png" alt=""></p>
<p><strong>这篇文档用尽量轻松的方式，带你把现在的主题体系从“为什么要这么做”一路串到“真实的代码是怎样运转的”。看完它，你就能在脑海里勾勒出这套系统的全过程：预设 → Token → CSS 变量 → 页面应用 → 生成脚本 → 运行时同步。</strong></p>
<hr>
<h2>热身：从最简单的主题方案说起</h2>
<p><strong>开始前先回顾一下大家最容易想到的做法，帮助你把这套体系的“前世今生”串起来：</strong></p>
<ol>
<li><p><strong>字符串配置 Ant Design Theme</strong>  ** **最初我们只需在 <code>ConfigProvider</code> 里写个对象：</p>
<pre><code>
&lt;ConfigProvider
  theme={{
    token: {
      colorPrimary: &#39;#1677ff&#39;,
      colorBgLayout: &#39;#f5f5f5&#39;,
    },
  }}
&gt;
</code></pre>
<p><strong>这种写法简单直观，但它只影响 Ant Design 组件，普通样式文件里的颜色、间距依旧得手动维护，一旦换主题就得处处搜字符串。</strong></p>
</li>
<li><p><strong>引入一个 CSS 变量做兜底</strong>  ** **于是我们可能会想到在全局样式里写：</p>
<pre><code>:root {
  --app-primary: #1677ff;
}
</code></pre>
<p>**自己的组件可以用 **<code>var(--app-primary)</code>，比硬编码好一点。但变量少、结构散乱，随着主题变多还是会失控：不同文件各自声明变量、缺乏注释、没有类型提示，团队协作很快陷入混乱。</p>
</li>
<li><p><strong>需求升级，开始寻找系统化思路</strong>  ** **当需要支持多套主题、明暗模式、甚至运行时切换时，就不得不思考：</p>
<ul>
<li>**这些颜色、尺寸到底从哪来？  **</li>
<li>**Ant Design 的 token 怎么复用到我们的样式里？  **</li>
<li><strong>有没有办法让主题列表、变量名称都能自动补全，减少低级错误？</strong></li>
</ul>
</li>
</ol>
<p><strong>带着这些痛点，我们才走向下一节——打造一套类型化、自动化、统一管理的主题体系。</strong></p>
<hr>
<h2>1. 目标：为什么要这么折腾？</h2>
<ol>
<li><strong>告别硬编码</strong>：以前的 SCSS 变量写死在文件里，换主题得全局搜一遍。现在把基础设计变量全部放进 TypeScript，让 IDE 帮我们补全并提示错误。</li>
<li><strong>统一管理</strong>：一份预设数据 + 一套生成脚本，就能衍生出所有模式（Light / Dark）与项目自定义主题，不再担心某个文件忘记更新。</li>
<li><strong>类型安全</strong>：Token 名称、CSS 变量名、DOM 属性统统“有类型”，手残都打不出错误的变量名。</li>
<li><strong>扩展友好</strong>：未来要加新主题、变更 Token、添加动态变换逻辑（transform）都很容易，只改一处即可触发全链路联动。</li>
</ol>
<hr>
<h2>2. 数据源：主题预设（<code>src/theme/tokens.ts</code>）</h2>
<p><strong>最核心的就是定义好“主题预设”：</strong></p>
<pre><code>// src/theme/tokens.ts
import type { SeedToken } from &#39;antd/es/theme/interface&#39;;

type TRequiredSeedTokenKeys = &#39;colorPrimary&#39; | &#39;colorInfo&#39;;
type TThemeAppearance = &#39;light&#39; | &#39;dark&#39;;

type TThemeSeedOverrides = Pick&lt;SeedToken, TRequiredSeedTokenKeys&gt; &amp;
  Partial&lt;SeedToken&gt;;

export interface IThemePreset&lt;K extends string = string&gt; {
  key: K;
  name: string;
  description: string;
  colors: Record&lt;TThemeAppearance, string&gt;;
  token: Record&lt;TThemeAppearance, TThemeSeedOverrides&gt;;
}

const createThemePreset = &lt;K extends string&gt;(preset: IThemePreset&lt;K&gt;) =&gt; preset;

export const THEME_PRESETS = [
  createThemePreset({
    key: &#39;shadcn&#39;,
    name: &#39;Shadcn&#39;,
    description: &#39;Shadcn 主题&#39;,
    colors: {
      light: &#39;oklch(12.9% 0.042 264.695)&#39;,
      dark: &#39;oklch(78% 0.042 264.695)&#39;,
    },
    token: {
      light: {
        colorPrimary: &#39;oklch(12.9% 0.042 264.695)&#39;,
        colorInfo: &#39;oklch(12.9% 0.042 264.695)&#39;,
      },
      dark: {
        colorPrimary: &#39;oklch(78% 0.042 264.695)&#39;,
        colorInfo: &#39;oklch(78% 0.042 264.695)&#39;,
      },
    },
  }),
  createThemePreset({
    key: &#39;purple&#39;,
    name: &#39;酱紫 Purple&#39;,
    description: &#39;艺术设计、创意类项目&#39;,
    colors: {
      light: &#39;#722ed1&#39;,
      dark: &#39;#9b6df0&#39;,
    },
    token: {
      light: {
        colorPrimary: &#39;#722ed1&#39;,
        colorInfo: &#39;#722ed1&#39;,
      },
      dark: {
        colorPrimary: &#39;#9b6df0&#39;,
        colorInfo: &#39;#9b6df0&#39;,
      },
    },
  }),
] as const;

export type TThemeKey = (typeof THEME_PRESETS)[number][&#39;key&#39;];
</code></pre>
<p><strong>然后我们把它整理成一个 Map，方便查找：</strong></p>
<pre><code>export const THEME_MAP = THEME_PRESETS.reduce((acc, preset) =&gt; {
  acc[preset.key] = preset;
  return acc;
}, {} as Record&lt;TThemeKey, IThemePreset&gt;);

// 使用示例
const selected = useAppStore((state) =&gt; state.themeKey);
const themePreset = THEME_MAP[selected];
</code></pre>
<ul>
<li><code>colors.light / colors.dark</code> 提供主题面板展示色，切换模式时色块同步更新；</li>
<li><code>token[light] / token[dark]</code> 喂给 Ant Design 生成完整 MapToken，保障组件主色一致；</li>
<li><strong>如果只配置了 light，生成脚本会自动退回该值作为所有模式的默认。</strong></li>
</ul>
<p><strong>好处</strong>：新增主题时有范可循，缺字段直接在编译阶段报错。</p>
<hr>
<h2>3. CSS 变量映射（<code>src/theme/cssVariables.ts</code>）</h2>
<p><strong>这个文件是整个改造的核心，做了几件事：</strong></p>
<ol>
<li><p><strong>定义常量</strong>：</p>
<pre><code>// src/theme/cssVariables.ts
export const THEME_ATTRIBUTE = &#39;data-theme-key&#39;;
export const THEME_MODE_ATTRIBUTE = &#39;data-theme-mode&#39;;
export const THEME_MODES = [&#39;light&#39;, &#39;dark&#39;] as const;
export type TThemeDomMode = (typeof THEME_MODES)[number];
</code></pre>
<p><strong>页面和脚本都用同一个常量，不怕手写拼错。</strong></p>
</li>
<li><p><strong>分组列出所有要生成的 CSS 变量</strong>：</p>
<pre><code>const COLOR_VARS = defineCssVars(
  [
    { name: &#39;--app-color-primary&#39;, token: &#39;colorPrimary&#39;, description: &#39;主色&#39; },
    { name: &#39;--app-color-link&#39;, token: &#39;colorLink&#39;, description: &#39;链接色&#39; },
    { name: &#39;--app-color-text&#39;, token: &#39;colorText&#39;, description: &#39;文本色&#39; },
  ] as const,
);

const BACKGROUND_VARS = defineCssVars(
  [
    { name: &#39;--app-bg-layout&#39;, token: &#39;colorBgLayout&#39;, description: &#39;布局背景色&#39; },
    { name: &#39;--app-color-fill&#39;, token: &#39;colorFill&#39;, description: &#39;填充色&#39; },
  ] as const,
);

export const CSS_VARIABLE_TOKEN_MAPPINGS = [
  ...COLOR_VARS,
  ...BACKGROUND_VARS,
] as const;
</code></pre>
<p><code>defineCssVars</code> + <code>as const</code> + <code>satisfies</code> 保留了变量名、映射 token 的字面量信息，后面 TypeScript 就能推导出 <code>TCssVarName</code>。</p>
</li>
<li><p><strong>静态变量同样类型化</strong>：</p>
<pre><code>export const STATIC_CSS_VARIABLES = defineStaticVars([
  { name: &#39;--app-layout-header-height&#39;, value: &#39;64px&#39;, description: &#39;头部高度&#39; },
  // ...
]);
export type TStaticCssVarName = (typeof STATIC_CSS_VARIABLES)[number][&#39;name&#39;];
</code></pre>
<p><strong>这些变量不随主题变化，但我们让它们也有注释、有类型。</strong></p>
</li>
<li><p><strong>辅助函数</strong>：**  ** <code>buildThemeSelector(presetKey, mode)</code> 可以返回 <code>:root[data-theme-key=&#39;purple&#39;][data-theme-mode=&#39;dark&#39;]</code>，脚本直接拿来用。</p>
</li>
</ol>
<p><strong>好处</strong>：</p>
<ul>
<li>**任何地方需要 **<code>--app-color-primary</code> 都能自动补全；</li>
<li>**新增变量时顺手填 **<code>description</code>，生成的 CSS 会带上注释，便于 DevTools 快速了解用途；</li>
<li><strong>静态变量和动态变量全部集中管理，真正做到“一本账”。</strong></li>
<li><strong>生成出来的 CSS 样子也更清晰，例如：</strong><pre><code>:root[data-theme-key=&#39;purple&#39;][data-theme-mode=&#39;light&#39;] {
  /* 主色 */
  --app-color-primary: #722ed1;
  --app-color-link: #722ed1;
  /* 布局背景色 */
  --app-bg-layout: #f5f5f5;
}
</code></pre>
</li>
</ul>
<hr>
<h2>4. 运行时代码（<code>src/utils/theme.ts</code> &amp; <code>src/App.tsx</code>）</h2>
<h3><code>src/utils/theme.ts</code></h3>
<ul>
<li><p><code>resolveThemeMode(themeMode, systemPrefersDark)</code>：把 store 里的 “light / dark / system” 转成真正的 <code>light</code> 或 <code>dark</code>。</p>
<pre><code>export const resolveThemeMode = (
  mode: TThemeMode,
  systemPrefersDark: boolean,
): TResolvedThemeMode =&gt; {
  if (mode === &#39;dark&#39;) return &#39;dark&#39;;
  if (mode === &#39;light&#39;) return &#39;light&#39;;
  return systemPrefersDark ? &#39;dark&#39; : &#39;light&#39;;
};
</code></pre>
</li>
<li><p><code>ThemeDom.apply(root, key, mode)</code>：往 <code>&lt;html&gt;</code>（我们用 document.documentElement）写入 <code>data-theme-key</code> 和 <code>data-theme-mode</code>；<code>clear</code> 用来卸载时清理。</p>
<pre><code>export const ThemeDom = {
  apply(root: HTMLElement, key: string, mode: TResolvedThemeMode) {
    root.setAttribute(THEME_ATTRIBUTE, key);
    root.setAttribute(THEME_MODE_ATTRIBUTE, mode);
  },
  clear(root: HTMLElement) {
    root.removeAttribute(THEME_ATTRIBUTE);
    root.removeAttribute(THEME_MODE_ATTRIBUTE);
  },
};
</code></pre>
</li>
<li><p><code>getCssVar(name, fallback)</code>：</p>
<pre><code>export const getCssVar = &lt;TName extends TCssVarName | TStaticCssVarName&gt;(
  name: TName,
  fallback = &#39;&#39;,
): string =&gt; {
  if (typeof document === &#39;undefined&#39;) return fallback;
  const value = getComputedStyle(document.documentElement)
    .getPropertyValue(name)
    .trim();
  return value.length &gt; 0 ? value : fallback;
};
</code></pre>
<ul>
<li>**支持类型联合 **<code>TCssVarName | TStaticCssVarName</code>；</li>
<li>**使用 **<code>getComputedStyle</code> 拿最终样式；</li>
<li>**没有值就返回 **<code>fallback</code>。</li>
</ul>
</li>
</ul>
<h3><code>src/App.tsx</code></h3>
<ul>
<li>**读取 store 中的主题 key / 模式，配合 **<code>resolveThemeMode</code> 算出最终模式；</li>
<li>**通过 **<code>useEffect</code> 调用 <code>ThemeDom.apply</code>，DOM 与 store 始终保持同步：<pre><code>const resolvedMode = resolveThemeMode(themeMode, systemPrefersDark);

useEffect(() =&gt; {
  setResolvedThemeMode(resolvedMode);
  if (typeof document === &#39;undefined&#39;) return;
  const root = document.documentElement;
  ThemeDom.apply(root, themePreset.key, resolvedMode);
  return () =&gt; ThemeDom.clear(root);
}, [resolvedMode, themePreset.key, setResolvedThemeMode]);
</code></pre>
</li>
<li><code>ConfigProvider</code> 使用主题预设中的 token，Ant Design 组件自动跟着换色：<pre><code>&lt;ConfigProvider
  theme={{
    algorithm: resolvedMode === &#39;dark&#39; ? darkAlgorithm : defaultAlgorithm,
    token: {
      colorPrimary: themePreset.token[resolvedMode].colorPrimary,
      colorInfo: themePreset.token[resolvedMode].colorInfo,
    },
  }}
&gt;
  &lt;AntdApp&gt;{/* ... */}&lt;/AntdApp&gt;
&lt;/ConfigProvider&gt;
</code></pre>
</li>
<li>**主题设置面板可以使用 **<code>preset.colors[resolvedMode]</code> 作为预览色块：<pre><code>const swatch = themePreset.colors[resolvedMode] ?? themePreset.colors.light;
&lt;span style={{ background: swatch }} className={styles[&#39;theme-swatch&#39;]} /&gt;
</code></pre>
</li>
<li><code>ThemeDom.clear</code> 确保热更新或卸载时不会留下陈旧属性。</li>
</ul>
<p><strong>好处</strong>：主题切换的状态流转完全可控，既更新 UI，又更新 DOM 属性，CSS 变量自然生效。</p>
<hr>
<h2>5. 生成脚本（<code>scripts/generate-css-variables.ts</code>）</h2>
<p><strong>核心逻辑非常集中，可以用这段代码概括：</strong></p>
<pre><code>const formatThemeBlocks = (): string =&gt; {
  const blocks: string[] = [];

  for (const mode of THEME_MODES) {
    const algorithm = ALGORITHMS[mode];
    for (const preset of THEME_PRESETS) {
      const seedOverrides =
        preset.token[mode] ?? preset.token.light ?? preset.token.dark;
      const seed = { ...defaultSeed, ...seedOverrides };
      const mapToken = algorithm(seed);

      const declarations = CSS_VARIABLE_TOKEN_MAPPINGS.map(({ name, token, description }) =&gt; {
        const rawValue = mapToken[token];
        const comment = description ? `${INDENT}/* ${description} */\n` : &#39;&#39;;
        return `${comment}${INDENT}${name}: ${String(rawValue)};`;
      });

      const selector = buildThemeSelector(preset.key, mode);
      blocks.push(`${selector} {\n${declarations.join(&#39;\n&#39;)}\n}`);
    }
  }

  return blocks.join(&#39;\n\n&#39;);
};
</code></pre>
<p>**执行 **<code>npm run generate:css-vars</code> 后，就会得到包含所有主题/模式变量的 <code>src/styles/generated/theme-variables.css</code>，暗色模式也会使用你在预设里定义的专属主色。</p>
<ul>
<li>**如果某个主题没有提供特定模式的 seed，脚本会依次回退到 **<code>light</code> 或 <code>dark</code> 的配置，避免生成空值。</li>
</ul>
<p><strong>好处</strong>：</p>
<ul>
<li><strong>任何变量增加/删除只动这一个 TypeScript 文件；</strong></li>
<li><strong>生成文件带注释，方便排查；</strong></li>
<li><strong>有重复变量名会直接抛错，避免覆盖。</strong></li>
</ul>
<hr>
<h2>6. 样式引用（<code>src/styles/global.scss</code> 等）</h2>
<ul>
<li>**统一使用 **<code>var(--app-xxx)</code> 提取主题色；</li>
<li>**通过 **<code>npm run generate:css-vars</code> 产生的 CSS 被 <code>src/main.tsx</code> 引入；</li>
<li>**页面结构中的具体样式（如 **<code>AdminLayout.module.scss</code>）只负责消费这些变量，不关心它们如何生成。</li>
</ul>
<pre><code>/* src/styles/global.scss */
:root {
  --layout-bg: var(--app-color-bg-layout);
  --text-color: var(--app-color-text);
  --card-border-color: var(--app-color-border-secondary);
}

[data-theme-mode=&#39;dark&#39;] {
  color-scheme: dark;
}
</code></pre>
<pre><code>/* src/layouts/AdminLayout.module.scss */
.admin-layout__theme-option {
  background: var(--card-bg);
  border: 1px solid var(--card-border-color);
  &amp;:hover {
    border-color: color-mix(in srgb, var(--app-color-primary) 40%, transparent);
  }
}
</code></pre>
<p><strong>好处</strong>：把“主题”这件事变成模块化的基础设施，新增页面只管引用变量，实现风格统一与换肤自如。</p>
<hr>
<h2>7. 常见操作流程</h2>
<ol>
<li><p><strong>新增主题</strong></p>
<ul>
<li>**在 **<code>THEME_PRESETS</code> 里加一项，同时补全 <code>colors.light/dark</code> 与 <code>token.light/dark</code>；</li>
<li><code>npm run generate:css-vars</code> → 得到新主题下的 CSS 变量；</li>
<li><strong>重启/刷新即可使用。</strong></li>
</ul>
</li>
<li><p><strong>新增 CSS 变量</strong></p>
<ul>
<li>**在 **<code>cssVariables.ts</code> 对应分组里补一行；</li>
<li><strong>需要的话在组件/样式中引用这个新变量；</strong></li>
<li><strong>跑生成脚本即可。</strong></li>
</ul>
</li>
<li><p><strong>读取变量值</strong></p>
<pre><code>import { getCssVar } from &#39;@/utils/theme&#39;;

const primary = getCssVar(&#39;--app-color-primary&#39;, &#39;#1677ff&#39;);
const headerHeight = getCssVar(&#39;--app-layout-header-height&#39;, &#39;64px&#39;);
</code></pre>
<ul>
<li><strong>如果变量不存在会返回你传入的默认值。</strong></li>
</ul>
</li>
</ol>
<hr>
<h2>8. 这套流水线的好处总结</h2>
<ul>
<li><strong>统一入口</strong>：所有主题数据和变量都在 <code>src/theme</code> 下维护，清晰可控。</li>
<li><strong>类型守护</strong>：<code>TThemeKey</code>、<code>TCssVarName</code>、<code>TStaticCssVarName</code> 等类型让 TS 帮你防止拼写错误。</li>
<li><strong>自动生成</strong>：脚本帮你把 Antd Token 转成 CSS 变量，不需要手动写多份颜色/布局参数。</li>
<li><strong>扩展简单</strong>：未来要实现 transform、modeScope 或更多模式，只要在映射里新增字段，脚本就能处理。</li>
<li><strong>易于协作</strong>：有了注释、分组、文档，后续同事加入项目也能快速理解主线。</li>
</ul>
<hr>
<h2>9. 推荐的命令清单</h2>
<table>
<thead>
<tr>
<th><strong>命令</strong></th>
<th><strong>作用</strong></th>
</tr>
</thead>
<tbody><tr>
<td><code>npm run generate:css-vars</code></td>
<td><strong>根据预设生成最新 CSS 变量</strong></td>
</tr>
<tr>
<td><code>npm run build</code></td>
<td><strong>构建项目，顺带验证类型和主题流程是否正常</strong></td>
</tr>
<tr>
<td><code>npm run dev</code></td>
<td><strong>开发模式，浏览器里实时查看主题效果</strong></td>
</tr>
</tbody></table>
<p>**建议在修改主题相关逻辑后，至少执行一次 **<code>generate:css-vars</code> + <code>build</code>，确保链路完整无误。</p>
<hr>
<h2>10. 接下来还可以做什么？</h2>
<ul>
<li>**编写简单的 lint 脚本，检测样式文件里使用的 **<code>var(--xxx)</code> 是否都存在于类型定义中；</li>
<li><strong>补充 transform / modeScope（之前预留的字段）以支持特殊处理，如暗色模式下再加一层透明度；</strong></li>
<li><strong>把 THEME_PRESETS 和 CSS 变量映射拆成更细的模块，编写 Storybook 文档展示主题效果。</strong></li>
</ul>
<p><strong>只要按照上面的流程维护，你就拥有了一套健壮、可扩展、可协作的主题解决方案。祝玩得开心！</strong></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[React i18n 浅解]]></title>
            <link>https://i5lin.top/blog/post/3553890047</link>
            <guid isPermaLink="false">3553890047</guid>
            <pubDate>Sun, 26 Oct 2025 12:52:46 GMT</pubDate>
            <description><![CDATA[今天，让我来分享一下自己对于 React i18n 的理解，从零开始配置 i18n 解决方案。]]></description>
            <content:encoded><![CDATA[<h1>React i18n 浅解</h1>
<p>今天，让我来分享一下自己对于 React i18n 的理解，并使用核心 lib: <code>i18next</code>和 <code>react-i18next</code>从零开始配置解决方案。</p>
<blockquote>
<p><code>react-i18next</code> 版本为：<code>16.1.5</code>，因此相关文档需要看 LeGACY v9 的部分：<a href="https://react.i18next.com/legacy-v9/step-by-step-guide">https://react.i18next.com/legacy-v9/step-by-step-guide</a></p>
</blockquote>
<p>前者是一个与框架无关的国际化库，无论 nodejs 和浏览器端都可以在工程化中使用。后者是 React 的适配层，且是基于 <code>i18next</code>的绑定层，有了它我们就可以在 <code>React</code>中轻松添加 i18n 支持。</p>
<p>项目仓库：<a href="https://github.com/AaronConlon/react-i18n-best-practice">AaronConlon/react-i18n-best-practice</a></p>
<h2>目标</h2>
<p>我的 i18n 方案实现了以下若干需求：</p>
<ul>
<li>常规多语言切换</li>
<li>语言选择持久化</li>
<li>浏览器语言检测</li>
<li>大型项目：resource 拆分和命名空间管理</li>
<li>TypeScript 项目下，翻译函数支持完善的自动补全</li>
<li>翻译文件懒加载，仅在需要的地方加载最小化翻译文件，并且让 i18next 支持 Suspense</li>
<li>翻译文件一致性检测脚本</li>
</ul>
<h2>行动</h2>
<h3>准备工作</h3>
<ol>
<li>使用 rsbuild 的命令，初始化一个 react 19 项目，选择支持 TypeScript 即可</li>
<li>安装 i18n libs<ol>
<li>react-i18next</li>
<li>i18next</li>
</ol>
</li>
<li>安装 @tanstack/react-router 和 @tanstack/router-plugin，并且根据文档添加 rsbuild 配置来启用 react-router</li>
<li>根据 <a href="https://github.com/TanStack/router/tree/main/examples/react/quickstart-rspack-file-based">https://github.com/TanStack/router/tree/main/examples/react/quickstart-rspack-file-based</a> 这个例子，配置好可运行的环境</li>
</ol>
<h3>常规多语言及切换</h3>
<p>先看看项目目录结构：</p>
<pre><code class="language-bash">.
├── node_modules
├── package.json
├── README.md
├── rsbuild.config.ts
├── src
│   ├── App.tsx
│   ├── env.d.ts
│   ├── i18n.ts
│   ├── index.tsx
│   ├── routes
│   │   ├── __root.tsx
│   │   ├── about.tsx
│   │   └── index.tsx
│   ├── routeTree.gen.ts
│   ├── styles.css
│   └── utils
├── tsconfig.json
└── yarn.lock
</code></pre>
<p>首先创建 <code>src/i18n.ts</code>初始化文件：</p>
<pre><code class="language-typescript">import i18n from &quot;i18next&quot;;
import { initReactI18next } from &quot;react-i18next&quot;;

const resources = {
  // 语言
  en: {
    // 默认命名空间
    translation: {
      &quot;Welcome to React&quot;: &quot;Welcome to React and react-i18next&quot;,
    },
  },
  fr: {
    translation: {
      &quot;Welcome to React&quot;: &quot;Bienvenue à React et react-i18next&quot;,
    },
  },
};

i18n.use(initReactI18next).init({
  resources,
  // 默认语言
  lng: &quot;en&quot;,
  // 备用语言
  fallbackLng: &quot;en&quot;,
  // 插值方式
  interpolation: {
    // 不转义 html 标签，避免 XSS 攻击
    escapeValue: false,
  },
});

export default i18n;
</code></pre>
<p>并且在 <code>src/index.tsx</code>中引入：</p>
<pre><code class="language-typescript">import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom/client&quot;;
import App from &quot;./App&quot;;
// 导入，初始化 i18n
import &quot;./i18n&quot;;

const rootEl = document.getElementById(&quot;root&quot;);

if (rootEl) {
  const root = ReactDOM.createRoot(rootEl);
  root.render(
    &lt;React.StrictMode&gt;
      &lt;App /&gt;
    &lt;/React.StrictMode&gt;
  );
}
</code></pre>
<p>然后看看 <code>routes</code>下的几个路由文件：</p>
<h3>__root.tsx</h3>
<pre><code class="language-typescript">import { createRootRoute, Link, Outlet } from &quot;@tanstack/react-router&quot;;
import { useTranslation } from &quot;react-i18next&quot;;

const RootLayout = () =&gt; {
  const { i18n } = useTranslation();

  return (
    &lt;&gt;
      &lt;div className=&quot;header&quot;&gt;
        &lt;Link to=&quot;/&quot; className=&quot;header-link&quot;&gt;
          Home
        &lt;/Link&gt;{&quot; &quot;}
        &lt;Link to=&quot;/about&quot; className=&quot;header-link&quot;&gt;
          About
        &lt;/Link&gt;
        {/* language selector */}
        &lt;select
          className=&quot;language-selector&quot;
          value={i18n.language}
          onChange={(e) =&gt; i18n.changeLanguage(e.target.value)}
        &gt;
          &lt;option value=&quot;en&quot;&gt;English&lt;/option&gt;
          &lt;option value=&quot;fr&quot;&gt;French&lt;/option&gt;
        &lt;/select&gt;
      &lt;/div&gt;
      &lt;Outlet /&gt;
    &lt;/&gt;
  );
};

export const Route = createRootRoute({ component: RootLayout });
</code></pre>
<p>通过 <code>useTranslation</code> hook 可以得到 <code>i18n</code>实例，这个实例具有当前语言属性和修改语言的方法。</p>
<p>接下来看 <code>src/routes/index.tsx</code>的内容：</p>
<pre><code class="language-typescript">import { createFileRoute } from &quot;@tanstack/react-router&quot;;
import { useTranslation } from &quot;react-i18next&quot;;

export const Route = createFileRoute(&quot;/&quot;)({
  component: RouteComponent,
});

function RouteComponent() {
  const { t } = useTranslation();
  return &lt;div className=&quot;content&quot;&gt;{t(&quot;Welcome to React&quot;)}&lt;/div&gt;;
}
</code></pre>
<p>还是核心 hook：<code>useTranslation</code>，通过 <code>t</code>函数传入翻译资源的字段来读取翻译的结果。</p>
<blockquote>
<p>如上所示是 react-i18next 最常规的使用方法，还有几种写法和在 react 组件外获取翻译内容，有兴趣可以去看文档</p>
</blockquote>
<p>现在，在 <code>/</code>路由下点击语言切换即可看到效果：</p>
<p><strong>英语</strong>：</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/10/6c31ca1794255fd73f2c2c3ba43513d5.png" alt="image-20251023043834284"></p>
<p><strong>法语</strong>
<img src="https://de4965e.webp.li/blog-images/2025/10/744847498c5856473bef5b7cbdc11946.png" alt="image-20251023043932544"></p>
<h3>语言持久化和语言检测</h3>
<p>如果仅需实现持久化，则简单在 <code>i18n.ts</code>这里直接将硬编码的默认语言改为从 localStorage 或 cookie 里读取即可，如此一来既可以让后端接口返回自动修改用户语言，也可以在修改语言的时候将选择的语言保存到本地。</p>
<p>我个人更喜欢另一种方案：<code>i18next-browser-languagedetector</code>插件，根据用户浏览器的语言来设置默认语言！</p>
<p>首先，安装好插件：</p>
<pre><code class="language-bash">yarn add i18next-browser-languagedetector 
</code></pre>
<p>然后再修改 <code>i18n.ts</code>初始化逻辑（忽略部分代码）：</p>
<pre><code class="language-typescript">import detector from &quot;i18next-browser-languagedetector&quot;;

i18n
  .use(detector)
  .use(initReactI18next)
  .init({
    resources,
    // 备用语言
    fallbackLng: &quot;en&quot;,
    // 插值方式
    interpolation: {
      // 不转义 html 标签，避免 XSS 攻击
      escapeValue: false,
    },
  });
</code></pre>
<blockquote>
<p>测试的浏览器语言是中文，因此在代码里加上中文相关的 resource 翻译和 select option 即可。</p>
</blockquote>
<p>此时，浏览器的 localStorage 里会自动加上 <code>i18nextLng</code> ，其值为 <code>zh</code>。</p>
<p><strong>i18next-browser-languagedetector</strong> 选择语言的默认优先级如下：</p>
<ul>
<li>cookie</li>
<li>localStorage</li>
<li>navigator</li>
<li>querystring (append <code>?lng=LANGUAGE</code> to URL)</li>
<li>htmlTag</li>
<li>path</li>
<li>subdomain</li>
</ul>
<p>因此，笔者建议将语言选项的值保存到 cookie。</p>
<blockquote>
<p>产品应该减少用户的操作，并且提供一种“顺畅”的用户体验。</p>
</blockquote>
<h3>大型项目：resource 拆分和命名空间管理</h3>
<p>企业级项目代码量较为庞大，将 resource 翻译的代码写在 <code>i18n.ts</code>里实在是太过拥挤，因此我们必须将这些翻译资源拆分到不同的模块下，通过 json 的格式保存，再统一导入。</p>
<p>看代码：</p>
<pre><code class="language-typescript">import i18n from &quot;i18next&quot;;
import detector from &quot;i18next-browser-languagedetector&quot;;
import { initReactI18next } from &quot;react-i18next&quot;;
import cn from &quot;./locales/cn.json&quot;;
import en from &quot;./locales/en.json&quot;;
import fr from &quot;./locales/fr.json&quot;;

const resources = {
  en: {
    translation: en,
  },
  fr: {
    translation: fr,
  },
  cn: {
    translation: cn,
  },
};

i18n
  .use(detector)
  .use(initReactI18next)
  .init({
    resources,
    // 备用语言
    fallbackLng: &quot;en&quot;,
    // 插值方式
    interpolation: {
      // 不转义 html 标签，避免 XSS 攻击
      escapeValue: false,
    },
  });

export default i18n;
</code></pre>
<p>首先，我们将翻译资源统一放在 <code>src/locales</code>下，按语言命名通过 json 导入，然后赋值给 <code>translation</code>（默认命名空间）。</p>
<p>当项目进一步扩大之后，翻译 json 文件将会变得越来越大，无论是查找还是修改都非常麻烦（在一个超大 json 文件中增删改查）。这时候，我们可以将翻译文件分为若干模块：</p>
<ul>
<li>Common (通用)</li>
<li>User（用户页面）</li>
<li>Dashboard（看板页面）</li>
</ul>
<p>这时候我们可以在 <code>locales</code>下创建多语言目录，并且在目录内创建若干个命名空间对应的 <code>json</code>文件：</p>
<pre><code class="language-bash">.
├── en
│   ├── common.json
│   ├── dashboard.json
│   └── user.json
├── fr
│   ├── common.json
│   ├── dashboard.json
│   └── user.json
└── zh
    ├── common.json
    ├── dashboard.json
    └── user.json
</code></pre>
<p>举例 <code>zh/common.json</code>：</p>
<pre><code class="language-json">{
  &quot;welcome&quot;: &quot;欢迎&quot;,
  &quot;loading&quot;: &quot;加载中...&quot;,
  &quot;error&quot;: &quot;错误&quot;,
  &quot;success&quot;: &quot;成功&quot;
}
</code></pre>
<p>在组件中通过命名空间使用：</p>
<pre><code class="language-jsx">import { createFileRoute } from &quot;@tanstack/react-router&quot;;
import { useTranslation } from &quot;react-i18next&quot;;

export const Route = createFileRoute(&quot;/&quot;)({
  component: RouteComponent,
});

function RouteComponent() {
  const { t } = useTranslation([&quot;common&quot;, &quot;user&quot;, &quot;dashboard&quot;]);
  return (
    &lt;div className=&quot;content&quot;&gt;
      {/* common namespace */}
      &lt;div className=&quot;common&quot;&gt;{t(&quot;common:welcome&quot;)}&lt;/div&gt;
      &lt;div className=&quot;common&quot;&gt;{t(&quot;common:loading&quot;)}&lt;/div&gt;
      &lt;br /&gt;
      {/* user namespace */}
      &lt;div className=&quot;user&quot;&gt;{t(&quot;user:profile&quot;)}&lt;/div&gt;
      {/* dashboard namespace */}
      &lt;div className=&quot;dashboard&quot;&gt;{t(&quot;dashboard:overview&quot;)}&lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>这里的钩子函数参数非常关键：<code>useTranslation([&quot;common&quot;, &quot;user&quot;, &quot;dashboard&quot;])</code>，不同的参数会设置不同的默认命名空间，当设置了默认命名空间的时候，就可以在 <code>t()</code>函数里缺省命名空间前缀。</p>
<p>例如：</p>
<pre><code class="language-jsx">import { createFileRoute } from &quot;@tanstack/react-router&quot;;
import { useTranslation } from &quot;react-i18next&quot;;

export const Route = createFileRoute(&quot;/&quot;)({
  component: RouteComponent,
});

function RouteComponent() {
  const { t } = useTranslation([&quot;common&quot;]);
  return (
    &lt;div className=&quot;content&quot;&gt;
      {/* common namespace */}
      &lt;div className=&quot;common&quot;&gt;{t(&quot;common:welcome&quot;)}&lt;/div&gt;
      &lt;div className=&quot;common&quot;&gt;{t(&quot;loading&quot;)}&lt;/div&gt;
      &lt;br /&gt;
      {/* user namespace */}
      &lt;div className=&quot;user&quot;&gt;{t(&quot;user:profile&quot;)}&lt;/div&gt;
      {/* dashboard namespace */}
      &lt;div className=&quot;dashboard&quot;&gt;{t(&quot;dashboard:overview&quot;)}&lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>直接写 <code>t(&quot;loading&quot;)</code>是可以得到翻译结果的，因为指定了 <code>common</code>命名空间。此时如果你写 <code>t(&quot;profile&quot;)</code>，那么是得不到翻译结果的。</p>
<p>如果不传参给 <code>useTranslation</code>，那么就会从 <code>i18n.ts</code>里的默认命名空间去寻找翻译结果。</p>
<p>函数 <code>t()</code>根据传入的 key 去获取翻译自有一套规则，并且可能会根据版本变化而更新。</p>
<p>与其花时间去折腾其规则，不如创建多个指定命名空间且名字不同的 <code>t</code>函数，亦或者统一在 <code>t()</code>函数传参的时候写明命名空间。</p>
<pre><code class="language-jsx">import { createFileRoute } from &quot;@tanstack/react-router&quot;;
import { useTranslation } from &quot;react-i18next&quot;;

export const Route = createFileRoute(&quot;/&quot;)({
  component: RouteComponent,
});

function RouteComponent() {
  const { t } = useTranslation();
  const { t: tUser } = useTranslation(&quot;user&quot;);
  const { t: tDashboard } = useTranslation(&quot;dashboard&quot;);
  return (
    &lt;div className=&quot;content&quot;&gt;
      {/* common namespace */}
      &lt;div className=&quot;common&quot;&gt;{t(&quot;welcome&quot;)}&lt;/div&gt;
      &lt;div className=&quot;common&quot;&gt;{t(&quot;loading&quot;)}&lt;/div&gt;
      &lt;br /&gt;
      {/* user namespace */}
      &lt;div className=&quot;user&quot;&gt;{tUser(&quot;profile&quot;)}&lt;/div&gt;
      {/* dashboard namespace */}
      &lt;div className=&quot;dashboard&quot;&gt;{tDashboard(&quot;overview&quot;)}&lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h3>TypeScript 编写体验优化</h3>
<p>如果你也使用 TypeScript 编写 React 代码，那么我想你会喜欢在使用 <code>t()</code>函数的时候填写 <code>key</code>获得编辑器的自动提示支持。</p>
<p>实现方案如下：</p>
<ul>
<li>微调 <code>i18n.ts</code>文件</li>
<li>创建 <code>src/i18next.d.ts</code>类型声明文件</li>
</ul>
<p>首先来看新的 <code>i18n.ts</code>文件：</p>
<pre><code class="language-typescript">import i18n from &quot;i18next&quot;;
import detector from &quot;i18next-browser-languagedetector&quot;;
import { initReactI18next } from &quot;react-i18next&quot;;
import zhCommon from &quot;./locales/zh/common.json&quot;;
import zhDashboard from &quot;./locales/zh/dashboard.json&quot;;
import zhUser from &quot;./locales/zh/user.json&quot;;

import enCommon from &quot;./locales/en/common.json&quot;;
import enDashboard from &quot;./locales/en/dashboard.json&quot;;
import enUser from &quot;./locales/en/user.json&quot;;

import frCommon from &quot;./locales/fr/common.json&quot;;
import frDashboard from &quot;./locales/fr/dashboard.json&quot;;
import frUser from &quot;./locales/fr/user.json&quot;;

export const defaultNS = &quot;common&quot;;

export const resources = {
  en: {
    common: enCommon,
    user: enUser,
    dashboard: enDashboard,
  },
  fr: {
    common: frCommon,
    user: frUser,
    dashboard: frDashboard,
  },
  zh: {
    common: zhCommon,
    user: zhUser,
    dashboard: zhDashboard,
  },
} as const;

i18n
  .use(detector)
  .use(initReactI18next)
  .init({
    resources,
    ns: [&quot;common&quot;, &quot;user&quot;, &quot;dashboard&quot;],
    defaultNS,
    // 备用语言
    fallbackLng: &quot;en&quot;,
    // 插值方式
    interpolation: {
      // 不转义 html 标签，避免 XSS 攻击
      escapeValue: false,
    },
  });

export default i18n;
</code></pre>
<p>变化在于：</p>
<ul>
<li>将默认命名空间导出</li>
<li>将 resource 导出，并且在末尾使用 <code>as const</code>来让 TypeScript 断言，告诉编译器这个对象缩窄后的类型。</li>
</ul>
<p>再看类型声明文件：<code>i18next.d.ts</code>:</p>
<pre><code class="language-typescript">import { defaultNS, resources } from &quot;./i18n&quot;;

declare module &quot;i18next&quot; {
  interface CustomTypeOptions {
    defaultNS: typeof defaultNS;
    resources: {
      common: (typeof resources)[&quot;en&quot;][&quot;common&quot;];
      user: (typeof resources)[&quot;en&quot;][&quot;user&quot;];
      dashboard: (typeof resources)[&quot;en&quot;][&quot;dashboard&quot;];
    };
  }
}
</code></pre>
<p>给 module <code>i18next</code>的 <code>CustomTypeOptions</code>接口声明默认命名空间和 resource 的类型，如此一来就可以在 <code>t()</code>这里自动补全 <code>key</code>了。</p>
<h3>JSON 懒加载</h3>
<p>直接通过 <code>import</code>语句导入 <code>json</code>文件，在打包的时候会把全部翻译资源打包到代码中去，显著增加 <code>bundle</code>尺寸。</p>
<p>在开始之前，明确自己的场景和需求，回答以下几个问题：</p>
<ul>
<li>能否需要 CDN</li>
<li>是否限制 Bundle 体积</li>
<li>是否运行于不稳定的网络</li>
<li>是否离线运行</li>
<li>是否需要严格确保加载资源的可用性</li>
</ul>
<p>得出答案之后，我们逐一分析以下场景选择的技术栈和实现方案：</p>
<h4>国内企业中后台（React SPA）</h4>
<p>通常企业后台对 SEO、首屏加载速度、弱网和离线、强可用都要求不高，选择 <code>i18next-resources-to-backend</code>插件足以。</p>
<pre><code class="language-typescript">import i18n from &quot;i18next&quot;;
import detector from &quot;i18next-browser-languagedetector&quot;;
import resourcesToBackend from &quot;i18next-resources-to-backend&quot;;
import { initReactI18next } from &quot;react-i18next&quot;;

i18n
  .use(detector)
  .use(
    resourcesToBackend(
      (lang: string, ns: string) =&gt; import(`./locales/${lang}/${ns}.json`)
    )
  )
  .use(initReactI18next)
  .init({
    ns: [&quot;common&quot;],
    defaultNS: &quot;common&quot;,
    // 备用语言
    fallbackLng: &quot;en&quot;,
    // 插值方式
    interpolation: {
      // 不转义 html 标签，避免 XSS 攻击
      escapeValue: false,
    },
  });

export default i18n;
</code></pre>
<p>所有初始化时设置的 <code>ns</code>都会在首次加载直接请求对应的 <code>chunk</code>文件，如上所示我的测试环境会立即请求：</p>
<pre><code class="language-bash">http://localhost:3000/static/js/async/src_locales_en_common_json.js
</code></pre>
<p>回过头来看，我们的目标还有一个 React Suspense 特性需要支持。为什么要这个特性？在本地开发的时候资源加载极快，你可能一不小心没注意到翻译的结果会出现闪烁，究其原因在于 i18next-react 在初次渲染的时候还没有获取到翻译的资源，于是会立即渲染对应的 key 的字符串内容。</p>
<p>所以，我们应该在这个阶段提供特定的 UI 来防止闪烁，看看如下代码：</p>
<pre><code class="language-jsx">import { createRootRoute, Link, Outlet } from &quot;@tanstack/react-router&quot;;
import { useTranslation } from &quot;react-i18next&quot;;

const RootLayout = () =&gt; {
  const { i18n, t } = useTranslation(&quot;menu&quot;);

  return (
    &lt;&gt;
      &lt;div className=&quot;header&quot;&gt;
        &lt;Link to=&quot;/&quot; className=&quot;header-link&quot;&gt;
          {t(&quot;home&quot;)}
        &lt;/Link&gt;{&quot; &quot;}
        &lt;Link to=&quot;/about&quot; className=&quot;header-link&quot;&gt;
          {t(&quot;about&quot;)}
        &lt;/Link&gt;
        &lt;Link to=&quot;/test&quot; className=&quot;header-link&quot;&gt;
          {t(&quot;test&quot;)}
        &lt;/Link&gt;
        {/* language selector */}
        &lt;select
          className=&quot;language-selector&quot;
          value={i18n.language}
          onChange={async (e) =&gt; {
            const newLang = e.target.value;
            // save to cookie
            // document.cookie = `i18nextLng=${newLang}; path=/`;
            await i18n.changeLanguage(newLang);
          }}
        &gt;
          &lt;option value=&quot;en&quot;&gt;English&lt;/option&gt;
          &lt;option value=&quot;fr&quot;&gt;French&lt;/option&gt;
          &lt;option value=&quot;zh&quot;&gt;中文&lt;/option&gt;
        &lt;/select&gt;
      &lt;/div&gt;
      &lt;Outlet /&gt;
    &lt;/&gt;
  );
};

export const Route = createRootRoute({
  component: RootLayout,
  pendingComponent: () =&gt; (
    &lt;div className=&quot;loading&quot;&gt;Loading at root layout...&lt;/div&gt;
  ),
});
</code></pre>
<p>在创建路由这里添加一个 Suspense fallback（pendingComponents），即可在渲染 RootLayout 组件的时候，让内部逻辑触发 Suspense 边界条件，从而渲染 loading 内容。</p>
<p><code>rsbuild</code>会将这个 json 文件打包成一个独立的 <code>js</code>文件，此外上述配置还加上了 react use suspense 支持：</p>
<pre><code class="language-typescript">{
  react: {
    useSuspense: true,
  }
}
</code></pre>
<p>现在，初始化加载语言翻译资源会触发 Suspense fallback 了！当然，上述代码仅在初始化的时候会渲染 fallback，初始化完成后再次切换语言将会请求新的语言翻译 chunk，此时不会触发 Suspense fallback，针对这一点我们可以单独使用 <code>useTransition</code>或者单独维护一个 loading 状态，在 <code>Outlet</code>组件下方渲染独立的 Loading Mask 组件（和 pendingComponent 共用）。</p>
<h4>出海应用 + 独立服务器部署（React SPA）</h4>
<p>独立服务器意味着资源存放在服务器这里，出海应用的用户可能来自不同的国家和地区，这时候使用 CDN 存放 JSON 翻译资源可以加快初始化和更换语言的速度。</p>
<p>使用 <code>i18next-resources-to-backend</code>依然可以做到这一点，不过我们需要将 <code>import</code>语句修改为 <code>fetch</code>远程 json 资源的逻辑。但是这一步细化一下也有一些问题需要处理，例如：缓存控制和版本处理、出错处理。</p>
<p>在这里我们需要考虑一个问题：翻译 JSON 文件是否需要在 Build 阶段构建成 js chunk，亦或是将翻译资源独立出来，存放在 OSS 上，利用 CDN 的能力提速，同时还可以灵活更新 OSS 文件，让应用不需要耗时构建整个应用。</p>
<p>有一个插件能很好地处理这个需求：<code>i18next-http-backend</code>，其核心是：让语言包成为“外部资源”，让国际化系统想加载数据一样加载翻译。</p>
<pre><code class="language-typescript">import i18n from &quot;i18next&quot;;
import detector from &quot;i18next-browser-languagedetector&quot;;
import HttpBackend from &quot;i18next-http-backend&quot;;
import { initReactI18next } from &quot;react-i18next&quot;;

i18n
  .use(detector)
  .use(HttpBackend)
  .use(initReactI18next)
  .init({
    backend: {
      loadPath:
        import.meta.env.PUBLIC_I18N_PATH + &quot;locales/{{lng}}/{{ns}}.json&quot;,
      crossOrigin: true,
      queryStringParams: {
        v: import.meta.env.PUBLIC_I18N_VERSION,
      },
    },
    ns: [&quot;common&quot;, &quot;menu&quot;],
    defaultNS: &quot;common&quot;,
    // 备用语言
    fallbackLng: &quot;en&quot;,
    // 插值方式
    interpolation: {
      // 不转义 html 标签，避免 XSS 攻击
      escapeValue: false,
    },
    react: {
      useSuspense: true,
    },
  });

export default i18n;
</code></pre>
<p>其核心在于配置项：</p>
<pre><code class="language-typescript">backend: {
  loadPath:
    import.meta.env.PUBLIC_I18N_PATH + &quot;locales/{{lng}}/{{ns}}.json&quot;,
  crossOrigin: true,
  queryStringParams: {
    v: import.meta.env.PUBLIC_I18N_VERSION,
  },
}
</code></pre>
<blockquote>
<p>现在，我们可以将 /locales 目录移动到 public 下</p>
</blockquote>
<p>我们通过环境变量去设置翻译文件的来源，在开发模式下读取&#39;/&#39;（public）的翻译文件，在生产环境读取 CDN 地址的资源。</p>
<p>如果项目复杂，我们也可以通过 API 获取用户更多信息，在这里修改其翻译资源的来源和版本等配置。</p>
<p>在用户请求 CDN 资源的时候，指定 queryStringParams 中的对象，通过这个机制来控制缓存，CDN 服务器根据 URL 来区分是否返回缓存，当然浏览器也一样。</p>
<p>我们可以通过一个 API 后端服务来统一下发用户的资源版本号和 CDN 地址（如果有必要的话），这个后端 API 既可以是独立于不同项目的，也可以是这个项目下配合的后端服务。</p>
<p>如果想做灰度测试，或者通过某个用户来测试线上环境的翻译效果，则可以单独给这个用户增加版本，从而触发用户浏览器缓存失效，最终从用户浏览器，经过 CDN 服务器，最终回源到 OSS 去请求最新的文件。</p>
<h4>离线应用、桌面应用、内网应用</h4>
<p>假设开发一个离线应用，亦或是可以读取到载体的文件系统的应用，我们可以将翻译资源通过主动下载的方式保存到用户的设备。</p>
<p>我们可以选择这个插件：<code>i18next-fs-backend</code></p>
<pre><code class="language-typescript">import i18n from &#39;i18next&#39;
import FsBackend from &#39;i18next-fs-backend&#39;
import { initReactI18next } from &#39;react-i18next&#39;
import path from &#39;path&#39;
import { fileURLToPath } from &#39;url&#39;

// __dirname 兼容 ESM
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

// 语言文件目录（绝对路径）
const localesPath = path.join(__dirname, &#39;./locales&#39;)

i18n
  .use(FsBackend)
  .use(initReactI18next)
  .init({
    backend: {
      loadPath: path.join(localesPath, &#39;{{lng}}/{{ns}}.json&#39;),
    },
    lng: &#39;zh&#39;,                 // 默认语言
    fallbackLng: &#39;en&#39;,
    ns: [&#39;common&#39;, &#39;dashboard&#39;],
    defaultNS: &#39;common&#39;,
    interpolation: {
      escapeValue: false,      // React 已自动防 XSS
    }
  })

export default i18n
</code></pre>
<p>离线应用的语言 JSON 都从 fs 直接读取，不依赖 HTTP 也不需要 CDN。</p>
<p>如果是内网应用，则可以提供一个服务上传翻译资源，再通过 fs 写入到指定的目录。</p>
<p>这些插件的写法非常相似，切换起来很容易。</p>
<h4>XXX 产品</h4>
<p>假设...</p>
<ul>
<li>产品用户来自不同地区，网络情况复杂无比</li>
<li>CDN 服务不稳定，加钱也没用</li>
<li>甲方要求高，千万不能出错</li>
<li>翻译内容频繁需要更新</li>
</ul>
<p>那么，我建议使用这个方案：</p>
<pre><code class="language-typescript">import i18n from &#39;i18next&#39;
import Backend from &#39;i18next-chained-backend&#39;
import LocalStorageBackend from &#39;i18next-localstorage-backend&#39;
import HttpBackend from &#39;i18next-http-backend&#39;
import resourcesToBackend from &#39;i18next-resources-to-backend&#39;
import { initReactI18next } from &#39;react-i18next&#39;

const CDN_PRIMARY = &#39;https://cdn1.yourcompany.com/locales&#39;
const CDN_SECONDARY = &#39;https://cdn2.backupcdn.com/locales&#39;
const LOCAL_VERSION = &#39;2025.10.26&#39; // 版本控制：可由 CI/CD 自动注入

i18n
  .use(Backend)
  .use(initReactI18next)
  .init({
    lng: &#39;zh&#39;,
    fallbackLng: &#39;en&#39;,
    ns: [&#39;common&#39;, &#39;menu&#39;, &#39;dashboard&#39;],
    defaultNS: &#39;common&#39;,
    backend: {
      // 1️⃣ 多层后端配置（顺序：缓存 → 主 CDN → 备 CDN → 本地 fallback）
      backends: [
        LocalStorageBackend,
        HttpBackend,
        HttpBackend,
        resourcesToBackend((lng, ns) =&gt; import(`../locales/${lng}/${ns}.json`)),
      ],
      backendOptions: [
        // --- LocalStorage 缓存层 ---
        {
          expirationTime: 7 * 24 * 3600 * 1000, // 缓存 7 天
          versions: {
            common: LOCAL_VERSION,
            menu: LOCAL_VERSION,
            dashboard: LOCAL_VERSION,
          },
        },
        // --- 主 CDN ---
        {
          loadPath: `${CDN_PRIMARY}/{{lng}}/{{ns}}.json`,
          crossDomain: true,
          queryStringParams: { v: LOCAL_VERSION },
        },
        // --- 备用 CDN ---
        {
          loadPath: `${CDN_SECONDARY}/{{lng}}/{{ns}}.json`,
          crossDomain: true,
          queryStringParams: { v: LOCAL_VERSION },
        },
        // --- 本地 fallback ---
        {}, // resourcesToBackend 不需要额外配置
      ],
    },
    interpolation: { escapeValue: false },
    react: { useSuspense: true },
  })

export default i18n
</code></pre>
<p>上述方案的功能如下：</p>
<ul>
<li>多 CDN 容错 - 两个 httpBackend</li>
<li>持久化存储 - 优先从 localstorage 读取</li>
<li>本地 fallback - 构建阶段同样将翻译资源打包成 chunk</li>
<li>自动切换逻辑 - <code>i18next-chained-backend</code>按顺序执行</li>
</ul>
<p>版本的控制和缓存更新可以参考之前提到过的方案（版本服务 API），多 CDN 的目标是让用户先从更稳定、更靠近用户的地方的服务商请求数据。</p>
<p>与此同时，应用也可以考虑支持一个清理换存的服务，让用户主动触发。</p>
<h3>一致性检测</h3>
<p>开发过程中修改翻译资源容易遗漏某个语言的信息，很有必要添加一个 script 在提交 Git 记录的时候提前检测翻译资源，以免字段没有对齐。</p>
<p>首先，添加相关 scripts 到 package.json 里：</p>
<pre><code class="language-json">{
  &quot;scripts&quot;: {
    &quot;check:i18n&quot;: &quot;node scripts/check-i18n-consistency.js&quot;,
    &quot;prepare&quot;: &quot;husky install&quot;
  }
}
</code></pre>
<p>创建在根目录的 scripts 目录下编辑 check-i18n-consistency.js:</p>
<pre><code class="language-js">#!/usr/bin/env node
/**
 * 检查 /locales/en/*.json 是否在其他语言下都有对应文件，
 * 并确保 JSON key 一致。
 */
import fs from &quot;fs&quot;
import path from &quot;path&quot;

const BASE_LANG = &quot;en&quot;
const LOCALES_DIR = path.resolve(&quot;./public/locales&quot;)

function getAllLangs() {
  return fs
    .readdirSync(LOCALES_DIR)
    .filter((f) =&gt; fs.statSync(path.join(LOCALES_DIR, f)).isDirectory())
}

function loadJson(filePath) {
  try {
    const content = fs.readFileSync(filePath, &quot;utf8&quot;)
    return JSON.parse(content)
  } catch (e) {
    console.error(`❌ 无法解析 JSON 文件：${filePath}`)
    process.exit(1)
  }
}

function getJsonKeys(obj, prefix = &quot;&quot;) {
  let keys = []
  for (const key in obj) {
    const full = prefix ? `${prefix}.${key}` : key
    if (typeof obj[key] === &quot;object&quot; &amp;&amp; obj[key] !== null) {
      keys = keys.concat(getJsonKeys(obj[key], full))
    } else {
      keys.push(full)
    }
  }
  return keys
}

// ----------------- 主逻辑 -----------------
const langs = getAllLangs()
if (!langs.includes(BASE_LANG)) {
  console.error(`❌ 缺少基准语言目录：${BASE_LANG}`)
  process.exit(1)
}

const baseFiles = fs
  .readdirSync(path.join(LOCALES_DIR, BASE_LANG))
  .filter((f) =&gt; f.endsWith(&quot;.json&quot;))

let hasError = false

for (const lang of langs.filter((l) =&gt; l !== BASE_LANG)) {
  console.log(`🔍 检查语言：${lang}`)

  for (const file of baseFiles) {
    const baseFile = path.join(LOCALES_DIR, BASE_LANG, file)
    const targetFile = path.join(LOCALES_DIR, lang, file)

    if (!fs.existsSync(targetFile)) {
      console.error(`❌ 缺少 ${lang}/${file}`)
      hasError = true
      continue
    }

    const baseJson = loadJson(baseFile)
    const targetJson = loadJson(targetFile)

    const baseKeys = getJsonKeys(baseJson)
    const targetKeys = getJsonKeys(targetJson)

    const missing = baseKeys.filter((k) =&gt; !targetKeys.includes(k))
    const extra = targetKeys.filter((k) =&gt; !baseKeys.includes(k))

    if (missing.length || extra.length) {
      console.error(`❌ ${lang}/${file} 键不一致:`)
      if (missing.length)
        console.error(`   缺少：${missing.join(&quot;, &quot;)}`)
      if (extra.length)
        console.error(`   多余：${extra.join(&quot;, &quot;)}`)
      hasError = true
    }
  }
}

if (hasError) {
  console.error(&quot;\n🚫 多语言文件结构不一致，请修复后再提交。&quot;)
  process.exit(1)
}

console.log(&quot;✅ 多语言文件检查通过！&quot;)
process.exit(0)
</code></pre>
<p>接着安装 <code>husky</code>并初始化：</p>
<pre><code class="language-bash">yarn add -D husky
yarn husky install
</code></pre>
<p>创建 pre-commit 钩子：</p>
<pre><code class="language-bash">mkdir -p .husky
touch .husky/pre-commit
chmod +x .husky/pre-commit
</code></pre>
<blockquote>
<p>husky v9 版本更新之后，配置可能有所不同</p>
</blockquote>
<p>编辑 pre-commit:</p>
<pre><code class="language-bash">echo &quot;✨ Running lint-staged and i18n check...&quot;
yarn run check:i18n
</code></pre>
<p>上述 sh 脚本会在 git 提交的时候执行 <code>check:i18n</code> 这个命令，我们也可以在其他时候执行以下检查字段有没有对齐。</p>
<p>好了，让我们来检查一下是否顺利。</p>
<pre><code class="language-bash">➜  react-i18n-best-practice git:(main) ✗ yarn check:i18n
yarn run v1.22.22
$ node scripts/check-i18n-consistency.js
🔍 检查语言：fr
🔍 检查语言：zh
✅ 多语言文件检查通过！
✨  Done in 0.96s.
➜  react-i18n-best-practice git:(main) ✗ 
</code></pre>
<p>将 <code>locales/zh/common.json</code>修改为：</p>
<pre><code class="language-json">{
  &quot;welcome&quot;: &quot;欢迎&quot;,
  &quot;loading&quot;: &quot;加载中...&quot;,
  &quot;error&quot;: &quot;错误&quot;,
  &quot;successssss&quot;: &quot;成功&quot;
}
</code></pre>
<p>再提交 git 让 husky 检查一次：</p>
<pre><code class="language-bash">➜  react-i18n-best-practice git:(main) ✗ gacm &quot;chore: test husky&quot;
✨ Running lint-staged and i18n check...
yarn run v1.22.22
$ node scripts/check-i18n-consistency.js
🔍 检查语言：fr
🔍 检查语言：zh
❌ zh/common.json 键不一致:
   缺少：success
   多余：successssss

🚫 多语言文件结构不一致，请修复后再提交。
error Command failed with exit code 1.
</code></pre>
<p>大功告成！</p>
<h2>最后</h2>
<p>好了，我的 React SPA 多语言方案大致已经梳理清楚了，如果你也有一些不同的见解，欢迎留言交流。</p>
<p>下一篇分析我将给大家带来 React SPA + Antd 的使用分析，通过其 token 机制制定多主题色系统，最终实现一个我们满意的风格。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.78]]></title>
            <link>https://i5lin.top/blog/post/3438424498</link>
            <guid isPermaLink="false">3438424498</guid>
            <pubDate>Sun, 21 Sep 2025 17:13:02 GMT</pubDate>
            <description><![CDATA[本期精选 8 大亮点工具/项目，聚焦 AI、生产力和开发效率。]]></description>
            <content:encoded><![CDATA[<h1>妙才周刊 No.78</h1>
<p>本期精选 8 大亮点工具/项目，聚焦 AI、生产力和开发效率。以下以简洁列表呈现，帮助你快速定位：</p>
<ul>
<li>Haye (AI 助手)核心亮点：macOS 私有 AI，支持多模型聊天（GPT-4.1/Claude 等）、文件/网页互动</li>
<li>OpenAI Codex (编码工具)核心亮点：GPT-5 驱动的编码代理，自动化 bug 修复/PR，CLI 集成</li>
<li>X Profile Menu (浏览器插件)核心亮点：Chrome 插件，悬停 Profile 弹出“冲浪”菜单，开源</li>
<li>Turso (数据库)核心亮点：LibSQL 基建，轻量云/本地存储，支持向量搜索，多语言 SDK</li>
<li>Bing Wallpaper Today (资源站)核心亮点：4K 必应壁纸合集，多年高清下载</li>
<li>npkill (清理工具)核心亮点：CLI 删除 node_modules，键盘一键节省空间</li>
<li>Filo Mail (邮件服务)核心亮点：AI+Gmail，自动总结待办/翻译回信/续费提醒</li>
</ul>
<h2>Haye</h2>
<p><img src="https://haye.ai/images/features/372i3ujs.png" alt=""></p>
<p>haye 是一个私有 macOS AI 助手程序，使您可以在一个应用中体验到深层搜索 R1、GPT-4.1、o3、Clude-3.7 和 Gemini-2.5 等顶级模型。简单来说，可以让用户在 macOS 上聊天，支持任何应用程序、文本、截图、文件。</p>
<p>主要功能：</p>
<ul>
<li>访问顶级 AI 模型：在一个应用中集成多个主流模型（如 Gemini、Claude、OpenAI、DeepSeek 和 Openrouter），支持同时与多个模型聊天并比较响应。</li>
<li>与桌面应用协作：无缝与 macOS 上的应用互动，适用于工作或学习任务。</li>
<li>网页搜索：连接互联网，提供快速、可信的答案，并附带相关网页来源链接。</li>
<li>与文件聊天：通过自然对话与 PDF 文档和本地文件互动，支持研究和内容理解。</li>
<li>便捷工具栏：使用简单快捷键快速查询文本和图像。</li>
</ul>
<p>这个应用为用户提供了基本服务，同时支持一次性买断和月度订阅。</p>
<h2>OpenAI Codex</h2>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1758474211811-b7a0fe00-8b38-4ae6-89f7-b67629f06b65.png" alt=""></p>
<p>Codex 是 OpenAI 推出的一系列 AI 编码工具，旨在帮助开发者通过云端和本地编码代理加速软件开发流程。它基于 GPT-5 等高级模型，专注于自动化编程任务，如编写功能、修复 bug、运行测试和提出拉取请求（PR）。</p>
<p>在 X 平台上，我看到很多开发者对 Codex 赞不绝口，并且认为其编程能力比 Claude 更强。Codex 可独立完成长任务，或与开发者互动；它模仿人类代码风格，并通过强化学习适应 PR 偏好。</p>
<p>只要你订阅了 ChatGPT Plus、Pro、Business、EDU 等计划即可使用，并且和 Claude 不同的是就算在 CLI 中使用完了阶段性的额度，也不会影响 web 端 Pro 等服务，并且其 Token 比 Claude 更加慷慨一些。</p>
<p>再加上 Claude 母公司对我们天然敌视（某种意义上），或许下个月我就要转投 Codex 了 🤣</p>
<h2>X Profile Menu</h2>
<p><a href="https://x-profile-menu-expansion-website.vercel.app/zh-CN">X Profile Menu - Chrome 浏览器插件</a> 是我开发的一款 chrome 插件，开发这个插件的目的是解决我一个推特冲浪需求：快速查看我喜欢的内容，甚至是创建一个独立的可随时查看的主页空间。</p>
<p>简单来说，即通过鼠标移动到 Profile 按钮上，弹出快捷菜单：</p>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1758468547377-fb20efa9-60ec-4958-a811-c199e0e3e75a.png" alt=""></p>
<p>既可以快速跳转个人页面，也可以在当前页面打开一个独立的空间，随时冲浪。</p>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1758468592470-b4909178-1326-42e2-a4d5-1eb5388cdcb6.png" alt=""></p>
<p>代码我开源在：<a href="https://github.com/AaronConlon/x-profile-menu-expansion">AaronConlon/x-profile-menu-expansion.</a>，欢迎给我一个 Star 支持我~ 感谢！</p>
<h2>Turso</h2>
<blockquote>
<p><a href="https://github.com/tursodatabase/turso">tursodatabase/turso: Turso Database is a project to build the next evolution of SQLite.</a></p>
</blockquote>
<p><img src="https://pbs.twimg.com/media/G05AYySa0AAxYwz?format=jpg&name=medium" alt=""></p>
<p>Turso 是一个基于 LibSQL（Rust 重写的 SQLite 分支，完全兼容 SQLite）的数据库服务，支持异步 I/O 和向量搜索。这个团队同时提供开源版本和云服务，云服务的免费额度对于独立开发者来说足以开发一个小型应用的存储需求，非常适合验证性的服务。</p>
<p>其 SDK 支持以下语言：</p>
<ul>
<li>Go</li>
<li>JavaScript</li>
<li>Java</li>
<li>Python</li>
<li>Rust</li>
<li>WebAssembly</li>
</ul>
<p>对于轻量化的本地存储来说，绝对值得了解一下。</p>
<h2>Bing 壁纸合集站点</h2>
<blockquote>
<p><a href="https://www.bingwallpaper.today/">Bing Wallpaper Today | Explore &amp; Download Daily Wallpapers in 4K</a></p>
</blockquote>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1758470399229-031d0406-d2f0-46c1-a34c-076d16775161.png" alt=""></p>
<p>Bing wallpaper today 是一个必应壁纸资源汇总网站，你可以在这里找到 4k 高清的最近几年的所有壁纸图片。</p>
<h2>npkill</h2>
<blockquote>
<p><a href="https://npkill.js.org/">NPKILL - Maintain your development workspace clean.</a></p>
</blockquote>
<p>npkill 是一个基于 nodejs 的命令行工具，它可以查看和清理指定目录下所有前端项目的 node_modules 目录，并且可以以一种非常简洁优雅的方式删除那些非必需的包，大大节省系统存储空间。</p>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1758470618107-e5ecbd91-8ac3-4d11-b0fa-4c94c0a18924.png" alt=""></p>
<p>我们可以通过键盘快捷键轻松清理不需要的目录。</p>
<h2>Filo Mail</h2>
<blockquote>
<p><a href="https://www.filomail.com/">Filo Mail - AI Email Client &amp; To-Do</a>：邮件诞生至今数十年，早已成为我们工作与生活的一部分，但它的核心体验，却始终未变。我们坚信，AI 时代的到来，正是重构这一重要工具的最佳时机。</p>
</blockquote>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1758472565048-95537af0-3c3c-424c-acbd-5ac736593aa8.png" alt=""></p>
<p>Filo 是一个 Google Mail + AI 服务合二为一的邮箱服务，前几期我推荐过一次，今天再次推荐给大家。</p>
<p>因为我切身体会到 AI 加持后的邮箱服务的好处，Filo 及时分析我的邮件内容，给我总结了待办事项，让我及时查看重要邮件，实实在在的帮助了我。</p>
<p>其核心能力如下：</p>
<ul>
<li>工作代办处理</li>
<li>订阅扣费续费警告</li>
<li>外文转译和辅助回信</li>
<li>内容总结</li>
</ul>
<p>产品团队更新非常快，并且也有移动端支持。</p>
<h2>Coomer</h2>
<p>Coomer 是...兄弟们...这是一个为了世界和平而创建的网络资源共享平台，全世界的兄弟们都把好东西共享出来了 🤣</p>
<h2>图</h2>
<center>IKIGAI</center><p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1757449891388-27d97060-6760-443f-9baa-218c0e435370.png" alt=""></p>
<h2>最后</h2>
<p>我在网上看到这句话，送给大家：“AI不會取代你的工作，用AI的人會。”</p>
<p>Bye！下周见。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.77]]></title>
            <link>https://i5lin.top/blog/post/3399819539</link>
            <guid isPermaLink="false">3399819539</guid>
            <pubDate>Tue, 09 Sep 2025 20:19:45 GMT</pubDate>
            <description><![CDATA[本期介绍了 Claude Code、Context7、UpAir、Surf、Streamdown、Keka、Magecdn 和 Cap 等工具，涵盖编程、文件管理、隐私保护和 UI 优化，助力开发者提升效率。]]></description>
            <content:encoded><![CDATA[<h1>妙才周刊 No.77</h1>
<p>大家好，欢迎查收本周的周刊资讯 🎉，本周给大家介绍了 Claude Code、Context7、UpAir、Surf、Streamdown、Keka、Magecdn 和 Cap 等工具，涵盖编程、文件管理、隐私保护和 UI 优化，助力开发者提升效率。</p>
<h2>Claude Code 概述</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/5c302f6ef23c7928a25baf8c9ed6a727.png" alt=""></p>
<blockquote>
<p><a href="https://docs.anthropic.com/zh-CN/docs/claude-code/overview">Claude Code 概述 - Anthropic</a></p>
</blockquote>
<p>了解 Claude Code，Anthropic 的智能编程工具，它运行在您的终端中，帮助您比以往更快地将想法转化为代码。</p>
<p>如果你喜欢 Claude Code，那么非常建议浅浅阅读一遍这个概述文档，它能让你对 Claude Code 的功能和日常使用方式有一个更好的理解。</p>
<h2>Claude Code 通知</h2>
<p>在 Claude Code 编写代码的时候，我可能在发呆。那么如何让 claude 提醒我它已经搞定了呢？</p>
<p>我找到一个方法，逐步执行以下命令：</p>
<pre><code class="language-bash">cd ~/.claude
vim settings.json
</code></pre>
<p>打开设置文件之后，将以下配置写入：</p>
<pre><code class="language-json">{
  &quot;hooks&quot;: {
    &quot;Stop&quot;: [
      {
        &quot;matcher&quot;: &quot;&quot;,
        &quot;hooks&quot;: [
          {
            &quot;type&quot;: &quot;command&quot;,
            &quot;command&quot;: &quot;afplay /System/Library/Sounds/Glass.aiff&quot;
          }
        ]
      }
    ]
  }
}
</code></pre>
<p>重启 Claude Code 即可让其完成任务之后调用系统的提示音，叮一下我😄</p>
<h2>Context7</h2>
<blockquote>
<p><a href="https://context7.com/">Context7 - Up-to-date documentation for LLMs and AI code editors</a></p>
</blockquote>
<p>Context7 是一个由 Upstash 开发的工具，主要用于为大型语言模型（LLM）和 AI 代码编辑器提供最新的、版本特定的代码文档和示例。</p>
<p>它通过索引官方文档（如 Next.js、Django、FastAPI 等框架），过滤并注入实时代码片段，帮助 AI（如 Claude）避免生成过时或错误的代码。Context7 支持手动复制粘贴，也可以通过 MCP（Model Context Protocol，模型上下文协议）服务器与 AI 工具集成，实现自动化注入文档。</p>
<p>在 Claude Code（Anthropic 的 Claude AI 代码生成工具）中使用 Context7 的核心优势是：Claude 可以动态查询最新文档，减少“幻觉”（hallucination）问题，例如生成已弃用的 API 或旧版本语法。</p>
<p>这特别适合 Python、JavaScript 等语言的开发，尤其在处理快速更新的库时。</p>
<h2>UpAir</h2>
<blockquote>
<p><a href="https://zerorust.cn/upair">UpAir - 快速云存储上传工具</a>，原生 APP 体积仅 500 kb</p>
</blockquote>
<p>UpAir 是一款专为 macOS 用户打造的强大直观文件上传工具，支持拖拽上传、自动链接复制，让云文件分享变得简单快速。</p>
<p>仅需配置你的 AWS S3 或 Cloudflare R2 端点，然后拖放文件到应用或托盘，即可上传并且获取链接，自动复制到粘贴板。</p>
<p><img src="https://vihv-test-bucket-20250901.s3.ap-southeast-1.amazonaws.com/Frame8.png" alt=""></p>
<p>📢：支持独立开发者，本软件内购一次性价格 28 RMB</p>
<h2>Surf</h2>
<blockquote>
<p><a href="https://enetx.surf/">enetx: g and surf</a></p>
</blockquote>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/335fb3342dff7da69aba5b40bb197f77.png" alt=""></p>
<p>Surf 是一个用 Go 编写的高级浏览器仿真（surf）库/工具，目标是在网络请求层面尽可能真实地模仿 Chrome 与 Firefox 的客户端指纹与传输特征，从而提高隐私、防止指纹追踪或用于自动化测试与反爬场景下的更高隐蔽性。其主要特性：</p>
<ul>
<li>浏览器伪装：支持 Chrome v131、Firefox v131 等多版本指纹</li>
<li>JA3/JA4 指纹定制：完全自定义 TLS 握手特征</li>
<li>HTTP/3 与 QUIC 支持：包含完整的 QUIC 传输参数指纹</li>
<li>中间件系统：可扩展的请求/响应处理链</li>
<li>连接池优化：单例模式实现高效连接复用</li>
<li>代理轮换：支持 HTTP、HTTPS、SOCKS5 代理自动切换</li>
</ul>
<p><strong>典型用例</strong>：</p>
<ul>
<li>反指纹测试与安全评估：检测服务器或 WAF 对浏览器指纹的识别与响应。</li>
<li>高仿真自动化爬虫：在需要更高隐蔽性的自动化抓取（合规与合法场景）中使用。</li>
<li>接口/兼容性测试：模拟不同浏览器组合，测试后端对不同浏览器行为的兼容性。</li>
<li>隐私保护代理：作为中间代理，替客户端对外展示不同指纹以保护隐私。</li>
</ul>
<h2>Streamdown</h2>
<blockquote>
<p><a href="https://streamdown.ai/">Streamdown</a> - 渲染流式 markdown 内容的神器</p>
</blockquote>
<p>Streamdown 是一个 Vercel 团队为 AI 驱动的流媒体设计的 react-markdown 的掉入式替代品。</p>
<p>Streamdown包含内置的 Tailwind 类，用于常见 Markdown 组件 —— 标题、列表、代码块等等。</p>
<p>此外，这个库对数学公式的支持非常强大，如果你需要考虑支持数学公式，那么这个库可以大大节省你的开发时间。</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/6857db2b77c20790bd5c221ac8959978.png" alt=""></p>
<p>流式返回的 AI 响应内容很多时候不是完整的 markdown 语法内容，常规的 react-markdown 无法渲染样式良好的 UI，这就是 Streamdown 发力的地方，使用这个库可以避免这个经典问题。</p>
<p>此外，Streamdown 还原生支持 GitHub 风味标记语言 (GFM)，因此您可以获得任务列表、表格等。</p>
<p>Streamdown 使用 Shiki 来突出显示代码块，并附带了一个复制按钮，这样您就可以轻松复制代码。鼠标悬停就能看到复制按钮！</p>
<p>让我们来看一个渲染 markdown 之中代码内容例子：</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/5f17a49bd0bd5344784e2d06a573cc02.png" alt=""></p>
<p>某种意义上，即使不需要渲染流式 markdown 也可以考虑这个库。</p>
<h2>Keka</h2>
<blockquote>
<p><a href="https://www.keka.io/zh-cn/">Keka - macOS 压缩文件管理器</a></p>
</blockquote>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/6a9499c548f4cda913f33f0b3ed4a227.png" alt=""></p>
<p>对于普通苹果电脑用户来说，在 macOS 上找到一个好用且支持的格式齐全的文件压缩和解压工具并不容易。</p>
<p>Keka 是一款专为 macOS 设计的压缩和解压文件管理器，它在功能、易用性和扩展性上远超 macOS 自带的 Archive Utility（默认解压工具）。</p>
<p>主要优势概述更广泛的格式支持：</p>
<ul>
<li>Keka 支持解压 30+ 种格式，包括 RAR、7Z、ZIPX 等常见但 Archive Utility 不支持的类型；压缩格式也更丰富（如 DMG、ISO），适合处理各种来源的文件。</li>
<li>高级功能：提供加密、分卷压缩和批处理，增强安全性和实用性，而默认工具几乎没有这些。</li>
<li>用户友好界面：拖拽式操作、更直观的进度显示和集成选项，让日常使用更高效。
性能与兼容性：虽未明确量化速度，但其优化设计（如分卷处理大文件）在实际场景中更可靠，尤其对老旧 macOS 版本有历史支持。</li>
<li>免费与开源：Keka 免费下载（App Store 也有付费版），并可设置为系统默认工具，提升无缝体验</li>
</ul>
<h2>Magecdn</h2>
<blockquote>
<p><a href="https://magecdn.com/tools/svg-loaders">Open Source SVG Loading Icons</a></p>
</blockquote>
<p>magecdn 免费提供 108 个可以自定义颜色的 SVG loading icon 下载使用</p>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1757447320972-b9fc3ac6-55d7-40de-870e-b29c4ea2a14d.png" alt=""></p>
<h2>Cap</h2>
<blockquote>
<p><a href="https://capjs.js.org/">Cap — Modern, lightning-quick PoW captcha</a></p>
</blockquote>
<p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1757447431280-d7826b52-d09b-4b18-9608-42f71cd8e6bd.png" alt=""></p>
<p>Cap 是一个轻量化的人机验证工具，支持自托管部署，接入非常简单，可以在国内产品中使用。</p>
<p>出海的服务可以使用 Cloudflare Turnstile 或者 reCAPTCHA，但是国内真的没有好的免费的方案，什么极验之类都特别贵，完全可以用这个来代替一下。</p>
<h2>图</h2>
<center>HR 对谈</center><p><img src="https://de4965e.webp.li/blog-images/2025/09/7cfd4f0a4032a5417928289afd03f0d9.png" alt=""></p>
<center>开源 PR</center><p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1757447685486-10344616-5769-4c1f-b7c6-8435184fb3f3.png" alt=""></p>
<center>猫</center><p><img src="https://pub-a24338d47a7449bcb524a571e22ffe97.r2.dev/1757447961757-aae6368d-704e-494f-9380-64faae2c5460.png" alt=""></p>
<h2>最后</h2>
<p>感谢阅读到此，大家下周再见 👋</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.76]]></title>
            <link>https://i5lin.top/blog/post/3373549528</link>
            <guid isPermaLink="false">3373549528</guid>
            <pubDate>Mon, 01 Sep 2025 18:52:53 GMT</pubDate>
            <description><![CDATA[本周推荐了多个有趣的开源项目和工具，涵盖多个领域。]]></description>
            <content:encoded><![CDATA[<p>本周推荐了多个有趣的开源项目和工具，涵盖多个领域。主要包括：</p>
<ol>
<li><strong>Filo</strong>：一款AI驱动的邮件客户端，支持邮件管理和待办事项。</li>
<li><strong>LimeWire</strong>：免费的文件传输网站，支持大文件传输和端到端加密。</li>
<li><strong>CheckCle</strong>：实时监控工具，提供服务器、应用程序和基础设施的监控功能。</li>
<li><strong>number-precision</strong>：一个轻量级（1k大小）的JavaScript库，用于精确的加减乘除操作。</li>
<li><strong>Evil Charts</strong>：开源React图表组件，支持多种动画和图表类型。</li>
<li><strong>Penpal</strong>：基于Promise改进iframe、Web Worker和窗口间通信的工具。</li>
<li><strong>doocs/md</strong>：支持微信公众号样式预览的Markdown编辑器，提供多种自定义功能和AI助手。</li>
</ol>
<h2>Filo 邮至即成</h2>
<p>邮件堆积成山？Filo 顺手理开：重点有了，待办好了，回复快了。</p>
<blockquote>
<p><a href="https://www.filomail.com/">Filo Mail - AI Email Client &amp; To-Do</a></p>
</blockquote>
<p>暂时仅发布了 macOS 版本的客户端，无缝链接多个 Gmail 账户，将 AI 能力发挥到邮件里，自动将邮件内容总结、分析从而提取出可能的 Task 任务，也可以润色和辅助翻译、发送邮件。</p>
<p>其中有一个扣费邮件分析和提醒的功能倒是很有亮点，此外也支持移动端 APP 下载。</p>
<p>笔者使用一周下来体验很好，不过 AI 相关的能力笔者倒是暂时没有涉及。</p>
<h2>Limewire 文件传输</h2>
<p>笔者给朋友传送一个文件的需求如下：</p>
<ul>
<li>不需要依赖任何需要注册登录的平台</li>
<li>至少能过传输 300M 文件</li>
<li>具有良好的传输速度</li>
</ul>
<p>此前国内有奶牛快传可满足需求，自从他们的服务升级了之后就不再符合笔者的要求了，于是我找到了：<a href="https://limewire.com/">LimeWire: Share Files - Secure File Upload Platform</a>！</p>
<p>这是一个开放的免费文件传输网站，支持最大 <code>4G</code>文件或目录传输和下载（端到端加密）和其他AI 相关功能，需要用的朋友可以看看。
<img src="https://de4965e.webp.li/blog-images/2025/09/f50c720cb1b295a8c29bb269e69e248d.png" alt="image.png"></p>
<h2>Checkcle.io</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/519677b700b54780b1d491e4043e3052.png" alt="image.png"></p>
<blockquote>
<p><a href="https://checkcle.io/">CheckCle - Self-Hosted Open Source Monitoring Tools for Real-time Servers &amp; Applications</a></p>
</blockquote>
<p>CheckCle 是一款开源解决方案，用于对全栈系统、应用程序和基础设施进行无缝实时监控。它为开发人员、系统管理员和 DevOps 团队提供其环境各层（无论是服务器、应用程序还是服务）的深入洞察和可操作数据。借助 CheckCle，你可以在整个技术堆栈中获得可见性、控制权以及确保最佳性能的能力。</p>
<h3>服务可用性与基础设施/服务器监控</h3>
<ul>
<li>监控 HTTP、DNS 和 Ping 协议</li>
<li>监控基于 TCP 的服务与 API（如 FTP、SMTP、HTTP）</li>
<li>跟踪详细的可用性（正常运行时间）、响应时间与性能问题</li>
<li>分布式区域监控</li>
<li>事件历史（UP / DOWN / WARNING / PAUSE：上线 / 下线 / 警告 / 暂停）</li>
<li>SSL 与域名监控（域名、签发者、到期日期、剩余天数、状态、上次通知）</li>
<li>基础设施与服务器监控：支持 Linux（🐧Debian、Ubuntu、CentOS、Red Hat 等）与 Windows（Beta）；提供 CPU、内存、磁盘使用、网络活动等服务器指标；一行命令安装的 Agent 脚本</li>
<li>维护计划与事件管理</li>
<li>运行状态 / 公共状态页</li>
<li>通过电子邮件、Telegram、Discord 和 Slack 通知</li>
<li>报告与分析</li>
<li>设置面板（用户管理、数据保留、多语言、主题〔深色/浅色模式〕、通知渠道与告警模板）</li>
</ul>
<h3>当前架构支持</h3>
<ul>
<li>✅ x86_64 PC、笔记本电脑、服务器 (amd64)</li>
<li>✅ 现代树莓派3/4/5（搭载64位操作系统）、Apple Silicon Macs (arm64)</li>
</ul>
<p>应用截图如下，其实还挺适合需要监控多个服务器和应用的场景的：
<img src="https://de4965e.webp.li/blog-images/2025/09/40273b58594f89ed201c90bc8eadcff8.png" alt="image.png"></p>
<h2>number-precision</h2>
<p><a href="https://github.com/nefe/number-precision">nefe/number-precision: 🚀1K tiny &amp; fast lib for doing addition, subtraction, multiplication and division operations precisely</a></p>
<p>number-precision 是一个仅 1k 大小的 JavaScript 库（支持 TypeScript 类型），专注于精确执行加减乘除等操作，避免 JavaScript 默认浮点计算所造成的精度误差。</p>
<p><strong>使用示例</strong>（典型用法）：</p>
<pre><code class="language-javascript">import NP from &#39;number-precision&#39;;

NP.strip(0.09999999999999998); // → 0.1
NP.plus(0.1, 0.2);             // → 0.3（而不是 0.30000000000000004）
NP.minus(1.0, 0.9);            // → 0.1（而不是 0.09999999999999998）
NP.times(3, 0.3);              // → 0.9（而不是 0.8999999999999999）
NP.divide(1.21, 1.1);          // → 1.1（而不是 1.0999999999999999）
NP.round(0.105, 2);            // → 0.11（而不是 0.1）
</code></pre>
<p>非常适合 <code>金额计算、科学计算、财务统计</code>等对精度要求高但是不超过 JavaScript 最大安全数范围的场景，如果你有其他类似需求（比如任意精度、复杂舍入规则等），也可以考虑更强大的库如 <code>big.js</code> 或 <code>decimal.js</code>（它们能处理任意精度的十进制运算）。</p>
<p>number-precision 的局限性在于 JavaScript 安全数的最大表示范围是 <code>2^53</code>（正负9,007,199,254,740,991），如果超过这个范围则推荐使用更强大的 <code>big.js</code>。</p>
<h2>Evil Charts</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/2902ff88a6fe7b215c6bb7718209facb.png" alt="image.png"></p>
<p><a href="https://evilcharts.com/">Evil Charts - Beautiful &amp; Animated Charts</a>是一个开源、即插即用的 React 图表组件，基于 Shadcn UI 的技术栈，非常适合熟悉 tailwind + Shadcn UI 的开发者。</p>
<blockquote>
<p>作者表示：“在寻找图表组件、平滑动画以及数据动态可视化时，我发现可选项较少。因此，我决定自己搭建一套。”</p>
</blockquote>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/45cd67a5c87e81a4bc01617aa64d9ec3.png" alt="image.png"></p>
<p>总的来说，这套组件的风格非常小清新，各种图表都配合着动画效果，代码示例非常完善，即插即用绝无问题。</p>
<h2>Penpal</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/228034437e36b14f139f3bff1cbe9283.png" alt="image.png"></p>
<p><a href="https://github.com/Aaronius/penpal">penpal</a> 使用基于 Promise 的方法改进 iframe、Web Worker 及窗口间的通信，相较于原生 postMessage 来说，更符合使用 Promise 的习惯。</p>
<p>推荐大家把这个工具加入到自己的前端开发工具箱中 😁</p>
<h2>md</h2>
<p>如果你想直接写 markdown 文档，同时可以查看到能够直接复制到微信公众号编辑器的效果，那么请看：</p>
<blockquote>
<p><a href="https://github.com/doocs/md?tab=readme-ov-file">doocs/md: ✍ WeChat Markdown Editor | 一款高度简洁的微信 Markdown 编辑器：支持 Markdown 语法、自定义主题样式、内容管理、多图床、AI 助手等特性</a></p>
</blockquote>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/654d80897e3c571d63947f03e9dc1d4b.png" alt="image.png">
左侧 markdown，右侧就是具有样式主题的公众号预览效果（内置主题，用户自选），此外还支持完善的图床上传和 AI 编辑、修改等功能。
其功能特性如下 ：</p>
<ul>
<li>支持 Markdown 所有基础语法、数学公式</li>
<li>供对 Mermaid 图表的渲染和 <a href="https://github.com/orgs/community/discussions/16925">GFM 警告块</a>的支持</li>
<li>提供 PlantUML 渲染支持</li>
<li>丰富的代码块高亮主题，提升代码可读性</li>
<li>允许自定义主题色和 CSS 样式，灵活定制展示效果</li>
<li>提供多图上传功能，并可自定义配置图床</li>
<li>便捷的文件导入、导出功能，提升工作效率</li>
<li>内置本地内容管理功能，支持草稿自动保存</li>
<li>集成主流 AI 模型（如 DeepSeek、OpenAI、通义千问、腾讯混元、火山方舟 等等），辅助内容创作</li>
</ul>
<h2>最后</h2>
<p>笔者会继续更新周刊内容，坚持做这件事。</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[前端开发实战笔记：从 Axios 到 TanStack Query 的工程化实践]]></title>
            <link>https://i5lin.top/blog/post/3370724038</link>
            <guid isPermaLink="false">3370724038</guid>
            <pubDate>Sun, 31 Aug 2025 22:11:05 GMT</pubDate>
            <description><![CDATA[本文仅作为记录我在看代码的过程和跟群友聊天了解到的一些知识。]]></description>
            <content:encoded><![CDATA[<h1>前端开发实战笔记：从 Axios 到 TanStack Query 的工程化实践</h1>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/4f3ed4b8276f90cd529de3340eb87d05.jpg" alt=""></p>
<h2>内容总览</h2>
<ul>
<li><code>Axios</code> header authorization 设置</li>
<li>iOS Safari 100vh 设置</li>
<li><code>Zustand</code> 保存主题和暗色模式</li>
<li><code>TanStack Query</code> 和 Toast 控制重试和通知</li>
<li><code>TanStack Query</code> 数据的有效期和缓存</li>
<li>C 端 SPA 鉴权和渲染</li>
</ul>
<h3>Axios token 设置</h3>
<p><code>Axios</code> 和 JWT 机制结合时，在客户端可以通过以下两种方式设置请求头 Authorization：</p>
<ol>
<li>拦截器设置</li>
<li>实例设置</li>
</ol>
<p>首先拦截器设置：</p>
<pre><code class="language-typescript">const instance = axios.create();

instance.interceptors.request.use((config) =&gt; {
  config.headers[&quot;Authorization&quot;] = &quot;Bearer &quot; + localStorage.getItem(&quot;token&quot;);
  return config;
});
</code></pre>
<p>其特点是动态性强，不怕 token 动态刷新，缺点是逻辑每次都走一遍，当然性能上几乎可以忽略。</p>
<p>其次是在 instance 上设置：</p>
<pre><code class="language-typescript">const instance = axios.create({
  baseURL: &quot;/api&quot;,
  headers: {
    Authorization: &quot;Bearer &quot; + localStorage.getItem(&quot;token&quot;),
  },
});

// 当 token 变化时，需要手动更新
instance.defaults.headers[&quot;Authorization&quot;] = &quot;Bearer &quot; + newToken;
</code></pre>
<p>只在初始化时或手动更新时设置，避免每次请求都走一遍，仅需要再刷新的时候重新设置。</p>
<blockquote>
<p>这对产品来说没有什么价值，但是对开发者的个人体感来说是有价值的。此前我都是使用拦截器，现在我更倾向通过实例来设置。</p>
</blockquote>
<p>对比一下二者：</p>
<table>
<thead>
<tr>
<th>方式</th>
<th>触发频率</th>
<th>动态性</th>
<th>手动维护</th>
<th>适用场景</th>
</tr>
</thead>
<tbody><tr>
<td><strong>拦截器设置</strong></td>
<td>每次请求</td>
<td>✅ 自动获取最新</td>
<td>❌ 不需要</td>
<td>token 可能过期/刷新</td>
</tr>
<tr>
<td><strong>instance.defaults 设置</strong></td>
<td>初始化/更新时</td>
<td>❌ 静态</td>
<td>✅ 需要手动更新</td>
<td>token 固定或生命周期较长</td>
</tr>
</tbody></table>
<h3>iOS Safari 100vh 设置</h3>
<p>在 iOS Safari 浏览器中，100 vh 的行为和浏览器不一致。</p>
<pre><code class="language-css">/* 在桌面浏览器中 */
.full-height {
  height: 100vh; /* 总是等于视口高度 */
}

/* 在 iOS Safari 中 */
.full-height {
  height: 100vh; /* 包含了地址栏和工具栏的高度！ */
}
</code></pre>
<p>直接看图说话，左侧全屏展示时是常规 100 vh，右侧为常规操作时页面，其 100 vh 可能和开发者期望的高度不一致。</p>
<p><img src="https://www.bram.us/wordpress/wp-content/uploads/2020/05/lld-minimal-vs-normal-ui@2x.png" alt="Image by Max Schmitt"></p>
<p>在滚动状态时甚至会隐藏地址栏，因此需要一个方案来实现兼容性更好的 100 vh 高度方案。</p>
<p>首先给出常见的方案总结：</p>
<table>
<thead>
<tr>
<th>方案</th>
<th>说明</th>
<th>优点</th>
<th>缺点</th>
</tr>
</thead>
<tbody><tr>
<td><code>height: 100vh</code></td>
<td>默认写法</td>
<td>简单</td>
<td>iOS Safari 上会导致页面被遮挡或溢出</td>
</tr>
<tr>
<td><code>-webkit-fill-available</code> fallback</td>
<td>CSS hack</td>
<td>可在 Safari 上更准确填充视口</td>
<td>嵌套层级限制、无法与 <code>calc()</code> 混用，对非 WebKit 有兼容性风险</td>
</tr>
<tr>
<td>JS + CSS 自定义 <code>--vh</code></td>
<td>动态计算并注入变量</td>
<td>高精度、可跨浏览器</td>
<td>需写 JavaScript，布局依赖 JS 性能</td>
</tr>
<tr>
<td><code>height: 100dvh</code></td>
<td>最新标准单位</td>
<td>最理想，响应式、自适配</td>
<td>需浏览器支持（如 Safari 15.4+, Chrome 108+, Firefox 101+）</td>
</tr>
</tbody></table>
<p>当真的需要兼容 iOS Safari 的时候，我才遇到这个问题。这里直接一步到位，跳过前两个方案。接下来再暂时放弃第四个方案，<code>dvh</code> 等新的单位是 2022 年提出的，现在使用恐怕会遇到兼容性问题，不如直接使用方案 3：<strong>JS + CSS 定义 css 变量</strong>。</p>
<p>上 React 代码（谢谢 UJ）：</p>
<pre><code class="language-typescript">import { useEffect } from &quot;react&quot;

/**
 * 修复 iOS Safari 下 100vh 不准确的问题
 * 自动设置 CSS 变量 --app-vh
 * 使用 requestAnimationFrame 限流，避免频繁操作 DOM
 */
export function useDomReadyReCalc() {
  useEffect(() =&gt; {
    if (typeof window === &quot;undefined&quot; || typeof document === &quot;undefined&quot;) return

    const docElm = document.documentElement

    // iOS 检测（含 iPad 桌面模式）
    const isIOS =
      /iPad|iPhone|iPod/.test(navigator.userAgent) ||
      (navigator.userAgent.includes(&quot;Mac&quot;) &amp;&amp; &quot;ontouchend&quot; in document)

    let ticking = false

    const updateVh = () =&gt; {
      if (!ticking) {
        requestAnimationFrame(() =&gt; {
          const vh = window.innerHeight * 0.01
          docElm.style.setProperty(&quot;--app-vh&quot;, `${vh}px`)
          ticking = false
        })
        ticking = true
      }
    }

    const setInitVh = () =&gt; {
      const vh = window.innerHeight * 0.01
      docElm.style.setProperty(&quot;--app-init-vh&quot;, `${vh}px`)
    }

    // ---- 初始计算 ----
    setInitVh()
    updateVh()

    // ---- 监听 resize ----
    window.addEventListener(&quot;resize&quot;, updateVh)

    if (isIOS) {
      // iOS 特殊处理
      window.addEventListener(&quot;orientationchange&quot;, updateVh)
      if (window.visualViewport) {
        window.visualViewport.addEventListener(&quot;resize&quot;, updateVh)
      }
    }

    // 清理事件
    return () =&gt; {
      window.removeEventListener(&quot;resize&quot;, updateVh)
      if (isIOS) {
        window.removeEventListener(&quot;orientationchange&quot;, updateVh)
        if (window.visualViewport) {
          window.visualViewport.removeEventListener(&quot;resize&quot;, updateVh)
        }
      }
    }
  }, [])
}
</code></pre>
<p>接着，在 React App.tsx 顶层调用一次即可在 CSS 中使用 css 变量来获取准确的高度。</p>
<h3>Zustand 使用细节</h3>
<p><code>Zustand</code> 在使用 store 中的嵌套属性时需要注意代码的写法，应该使用选择性订阅而不是订阅整个 store，订阅整个 store 会大大增加渲染次数。</p>
<pre><code class="language-typescript">export function useIsDarkMode() {
  // forSTORE
  const theme$_isDarkMode = useThemeStore((s) =&gt; s.theme$_isDarkMode);
  const theme$_appTheme = useThemeStore((s) =&gt; s.theme$_appTheme);
  // forSTORE

  const { isSysDarkMode } = useIsSysDarkMode();

  return {
    isDarkMode:
      // 看主题
      theme$_appTheme
        ? // 如果有选择过，就按 theme 走
          theme$_isDarkMode
        : // 不然按 sys 走
          isSysDarkMode,
  };
}
</code></pre>
<p>选择 store 中的某个字段时，应该使用选择性订阅的语法来确保 store 更新的影响范围最小。</p>
<h3>TanStack Query 和 Toast</h3>
<p><code>TanStack Query</code> 可以在创建 queryClient 的时候对整个 client 做通用配置，其中有一个 retry 的点引起了群友的讨论。</p>
<p>请求在出错、超时的情况下添加重试机制是一种很好的实践。<code>TanStack Query</code> 在默认情况下 retry 次数是 3 次，总共请求四次，有时候我们不需要重试这么多次，并且重试的时候可能会触发 <code>Axios</code> 拦截器中响应的逻辑处理，从而连续弹出多次 Toast。</p>
<p>这里面有两个优化点，首先是 retry 重试机制。将 retry 设置为 false 仅在特殊场景下（产品特性）可选，大多数情况下我们会自定义重试次数。看代码：</p>
<pre><code class="language-typescript">retry: (failureCount, error) =&gt; {
  if (error.status === 404) return false
  return failureCount &lt; 2
}
</code></pre>
<p>针对性 <code>404</code> 状态码不需要重试，针对重试次数来判断是否需要重试，从而设置重试次数。通过函数来控制重试机制，灵活性大大提升，我们可以在这里做更多的逻辑处理工作，例如 Toast 提示。</p>
<p>此前，笔者尝尝碰到在 <code>Axios</code> 拦截器中写 Toast 的业务需求，于是笔者在群里提出了自己的解决方案：<strong>通过请求 url、query params、body 进行序列化处理成唯一的 key 来区分请求，利用防抖机制防止快速或重复的 Toast</strong>。</p>
<p>这种方案大伙觉得麻烦，Bruce 提出给 Toast 配置 maxCount 为 1，这样多次重试可以保证页面仅单个 Toast 效果，看起来不会像连续多次 Toast 那样差劲。</p>
<p>但是笔者觉得这样会有问题，同时发生的若干接口可能会需要展示若干个 Toast 内容，限制最大数量将会隐藏先触发的 Toast，如果产品团队不能接受这一点，那么我们就得换一个方案。</p>
<p>最后，提出问题的老徐直接建议我们把逻辑放在 retry 函数中来处理，直接把话题给终结了。</p>
<p>挺好，在 retry 中针对次数来决定是否需要 Toast，既防止连续相同的 Toast 效果不好，也隔离了不同的请求的 Toast 影响，不需要担心多个请求异常的 Toast 展示遗漏，也不需要处理复杂的请求唯一性检查 🚀。</p>
<h3>TanStack Query 数据有效期和缓存</h3>
<p><code>TanStack Query</code> 能够将请求进行缓存，从而减少服务器压力，也能在特殊场景下快速响应数据，减少白屏。</p>
<p>来吧直接说场景：表格翻页渲染，我们希望给用户最佳的翻页体验和快速响应。</p>
<p>其中有两个关键的配置如下：</p>
<ul>
<li>gcTime 数据缓存时间</li>
<li>staleTime 数据有效时间</li>
</ul>
<p>在表格翻页的时候，如果希望能够立即使用缓存的数据作为响应立即展示，并且同时请求 API 获取数据来更新缓存，那么可以参考如下代码：</p>
<pre><code class="language-typescript">// 默认 Query 选项
export const defaultQueryOptions = {
  staleTime: 1000, // 1s数据保鲜期
  gcTime: 10 * 1000,   // v5 中 cacheTime 改名为 gcTime
  retry: 2, // 忽略 toast 逻辑
  retryDelay: (attemptIndex: number) =&gt; Math.min(1000 * 2 ** attemptIndex, 30000),
  refetchOnWindowFocus: false,
  refetchOnReconnect: true,
};

// 创建 QueryClient 实例
export const createQueryClient = () =&gt; {
  return new QueryClient({
    defaultOptions: {
      queries: {
        ...defaultQueryOptions,
        throwOnError: false,
      },
      mutations: {
        retry: 1,
        throwOnError: false,
      },
    },
  });
};
</code></pre>
<p>接下来请求数据：</p>
<pre><code class="language-typescript">export const useUsers = (params: TableParams) =&gt; {
  const queryClient = useQueryClient();

  const query = useQuery({
    queryKey: userQueryKeys.list(params),
    queryFn: () =&gt; fetchUsers(params),
  });
</code></pre>
<p>如此一来，切换页面的时候如果在 1s 内，则走缓存数据并且不触发 API 请求。如果在 1s 后则立即渲染缓存数据，同时走 API 请求，拿到最新数据之后立即同步 UI 和更新缓存。</p>
<p>开发者可以自行选择 1s 的窗口时间，降低服务器压力。如果有需要我们甚至可以把 staleTime 改为 30s，并且使用 prefetchQuery 提前加载下一页、上一页的数据，从而让用户对分页的数据响应获得更快的体验。</p>
<p>预加载是平衡性能和体验很好的一种方案。</p>
<h3>C 端 SPA 鉴权和渲染：数据优先，渲染在后</h3>
<p>在 C 端，单论 SPA 的场景来说笔者的观点如下：</p>
<ul>
<li>全局初始化优先于路由渲染</li>
<li>Suspense + 动态组件加载的结合</li>
<li>鉴权逻辑非常重要</li>
</ul>
<p>笔者认为在渲染路由之前，应该执行的是初始化的一些逻辑。</p>
<p>如若任何一个路由都使用 Suspense 和动态加载组件，那么路由在加载之前就应该读取应用的配置，并且初始化 <code>Zustand</code> 的 Store 数据，在初始化完成之前，都展示 Suspense 的 fallback 组件，这是一个全路由通用的组件，甚至可以加上一些品牌相关的 UI 和动画。</p>
<p>另外诸如多语言、主题和设置、CSS 变量、鉴权等都在渲染路由之前初始化，其他的暂时不说，直接说鉴权。</p>
<p>在渲染路由之前，首先得判断当前 token 有效性，这时候就可以通过异步逻辑去获取用户信息，从而确保应用启动之后拿到的是最新的用户信息。</p>
<p>如若直接在 login 的时候将信息和 token 保存到 localStorage，然后直接渲染路由，那么在 token 过期的时候就可能会导致多个组件先后请求自己的数据，并且引发 401 之后的多个逻辑。亦或是 userInfo、token 变更，将 localStorage 中的 userInfo 作为初始值也得处理信息变更的逻辑，否则用户的姓名甚至都可能变化了，UI 还展示旧的信息。</p>
<blockquote>
<p>用户信息和 token 有什么必要保存到 localStorage 中呢？我想仅有的原因是用户的个人习惯是通过浏览器刷新功能来重新查看数据。如果真有这部分考量，那么确实应该保存到 localStorage，并且可以结合默认读取 localStorage，同时通过独立的鉴权请求去更新用户信息和 token。</p>
</blockquote>
<p>通过 <code>Zustand</code> 初始化数据，那么组件就不需要再通过重复逻辑获取配置数据。其次，Suspense fallback 可以防止白屏和闪烁，也可以让动态加载的好处提现出来，初始 bundle 将会变得更小，每一个路由都拆分成独立的包，用户访问某个路由下才下载对应的代码。</p>
<p>渲染页面之前确保数据是存在的，组件内的代码也可以减少很多不存在的数据逻辑。</p>
<p>如果你也赞同笔者的想法，那么一定要看看这个库：<a href="https://tanstack.com/router/latest">TanStack Router</a></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/09/2de2023cee3543341795a878c7609297.png" alt="image-20250901053404026"></p>
<p>这个库简直是，啧啧啧。</p>
<p>无懈可击。</p>
<p>在 React 生态下提供完善的 TypeScript 支持、绝佳的路由定义（基于文件目录和配置）机制、独特的路由渲染前上下文控制、数据请求配置、Suspense 机制加持懒加载功能、完善的 URL 序列化和反序列化支持（代替 nuqs）等等。</p>
<p>堪称 <code>Tanstack Query</code> 的绝配。</p>
<h2>最后</h2>
<p>笔者考虑了很久，最终决定将本文作为付费内容进行发布，我希望自己生产的内容是有价值的，即使其价值很低（😞）。</p>
<p>另外，周刊的内容依然会免费更新，给大家带来自己接触到的新玩意。</p>
<p>Bye.</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Dia? AI + 浏览器 = Dia！]]></title>
            <link>https://i5lin.top/blog/post/3208713660</link>
            <guid isPermaLink="false">3208713660</guid>
            <pubDate>Mon, 07 Jul 2025 11:51:02 GMT</pubDate>
            <description><![CDATA[Dia 浏览器主打极简设计和 AI 深度集成，支持自然语言交互、内容总结和自动化操作。基础功能免费，高级 AI 功能或将付费。适合轻量办公，重度用户可观望后续更新。]]></description>
            <content:encoded><![CDATA[<p><img src="https://de4965e.webp.li/blog-images/2025/06/a92abb70d15ecd2974908b9d8cad0dea.png" alt=""></p>
<h2>前言</h2>
<p>我接触的第一个浏览器是 IE，随着上网经验的积累，IE 渐渐变成了用来下载 Chrome 的工具。在 2022 年以前，Chrome 始终是我最常用的浏览器。2022 年发生了什么？</p>
<p>2022 年，Arc 浏览器横空出世，产品团队以“重新想象浏览器”为 Slogan，为 Arc 赢得了大量忠实用户。</p>
<p>Arc 浏览器极具辨识度的左侧侧边栏、空间概念、垂直标签页和丰富的自定义能力，吸引了众多数字原住民和极客用户，口碑极佳！</p>
<p>然而，Arc 的创新也带来了使用门槛——对于许多习惯了 Chrome、Safari 传统布局的用户来说，Arc 的操作模式过于新颖，学习成本较高。根据官方数据，Arc 的核心特色功能（如多空间、Live Folders 等）实际使用率并不高，绝大多数用户并未充分利用这些“生产力利器”。</p>
<h2>新的道路</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/a92abb70d15ecd2974908b9d8cad0dea.png" alt="image.png"></p>
<p>2025 年，Arc 开发团队决定不再开发新功能，现有团队的主要精力已全面转向新一代 AI 浏览器 Dia。Arc 目前仅会继续进行安全补丁和必要的维护，不再进行功能性更新或大版本迭代。</p>
<p>团队认为，AI 驱动的浏览器才是未来的突破口，因此决定“推倒重来”，以 Dia 为新起点，打造更简洁、速度更快、AI 深度集成的新一代浏览器。</p>
<p>Dia 基于 Chromium 内核，界面风格更接近 Chrome，采用传统的水平标签页布局，并将 AI 助手原生集成到侧边栏与地址栏中。与 Arc 的“重塑浏览器交互”不同，Dia 更强调“让 AI 融入每一个上网动作”：用户可以随时向 AI 提问、总结当前标签页内容、跨标签比较信息，甚至通过“Skills”自定义一系列自动化操作（如写作辅助、代码生成、表单填充等）。</p>
<blockquote>
<p>Dia 社区呼唤开发团队将 Arc 的优点集成到 Dia：垂直标签页、Spaces、快捷键自定义</p>
</blockquote>
<p>Dia 的研发初衷是响应 AI 技术的爆发式普及和用户需求的转变。The Browser Company 的 CEO Josh Miller 直言，Arc 虽然获得了小众极客群体的热爱，却难以打动更广泛的主流用户。“我们发现，AI 聊天已经成为年轻用户的日常习惯。与其让用户不断切换到 ChatGPT，不如直接把 AI 深度融入浏览器，成为每一次上网的天然助手。”Dia 的“Skills”系统被比作 AI 时代的 App Store，未来有望根据场景自动调用不同的 AI 能力，甚至支持第三方开发者扩展。</p>
<p>在隐私与安全方面，Dia 宣称绝大多数数据本地加密存储，仅在调用 AI 服务时临时上传必要内容，且服务商不得保存或训练相关数据。这一“隐私优先”的策略，旨在化解用户对 AI 浏览器“过度采集数据”的担忧。</p>
<h2>用户反馈</h2>
<blockquote>
<p>笔者在 reddit 上看到一篇 Dia 用户的文章和其他留言，<a href="https://www.reddit.com/r/diabrowser/comments/1l95wzu/dia_browser_first_impressions/">传送门</a>。</p>
</blockquote>
<p>Dia 的极简设计、AI 原生集成、快速响应等特性获得了用户的认可，尤其是在内容写作、资料整理、学习辅助等场景下，AI 能够节省大量繁琐操作。</p>
<p>“我习惯在 Cursor 里用 AI 辅助编程，现在在 Dia 里也能边看网页边和 AI 深度对话，非常高效。”</p>
<p>但他们同样期待 Dia 尽快补齐标签页管理、空间分组等核心功能。（Dia 官方在社交媒体上回复用户，这部分功能将会在未来两周上线。）</p>
<p>另一个讨论热点是“收费模式”与“数据隐私”。不少用户担心，Dia 未来会不会像部分 AI 工具一样走高价订阅路线，或者对个人数据进行过度采集。官方目前的说法是：“基础浏览器免费，高阶 AI 功能可能付费”，但实际策略尚未完全明朗。</p>
<blockquote>
<p>Dia 调查问卷：如果我们对使用Dia收费，那合理的价格是多少？</p>
</blockquote>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/7530940b74d2057d4b4c74d37614467e.png" alt="image.png"></p>
<p>免费到底？不可能，AI 服务始终是资金堆起来的，最后大概率是订阅的价格拉扯问题罢了。</p>
<p>对于普通用户而言，Dia 的极简设计和 AI 辅助能带来一定便利，适合日常轻量办公、内容消费和简单自动化场景；对于重度生产力用户，当前版本的功能还远未达到 Arc 的高度，建议观望后续迭代。</p>
<p>如果你对 AI 驱动的浏览器体验充满好奇，愿意尝试新工具，并且对标签页管理等高级需求不那么刚性，可以申请试用 Dia，感受“AI+浏览器”带来的新体验。但如果你已经习惯了 Arc 的垂直标签、空间分组、深度自定义等特性，Dia 当前还不足以成为完全替代方案。未来，随着功能补齐和生态完善，Dia 能否真正成为“AI 时代的主力浏览器”，仍有待市场和用户的进一步检验。</p>
<h2>我的使用体会</h2>
<p>前段时间我下载了 Dia，最近办公也在高频使用 Dia 浏览器，个人非常喜欢 Dia 的 UI 和交互。</p>
<p>让我印象深刻的功能有：</p>
<ul>
<li>浏览器可以执行脚本，用户可以通过自然语言和浏览器 AI 交互，例如：我最近七天看了哪些 YouTube 链接的视频？AI 能够从历史记录中分析数据并反馈结果</li>
<li>随处可 Chat with AI！任选内容和页面就可以通过快捷键唤起 AI Chat 功能</li>
<li>浏览器级别的窗口拆分，像 terminal 一样可以左右分屏，且完全是独立窗口而非 Iframe，这一点是插件无法实现的，我一直对此非常遗憾</li>
<li>新建标签页设计得非常好，可以通过交互轻松切换 Chat 模式和搜索模式，输入内容的智能推测也很出色</li>
<li>UI 非常干净，极简风格让我耳目一新，细节打磨得非常好，例如 URL 不仅仅是 URL，也包含页面的 Title 等等信息</li>
<li>自定义命令支持：创建自定义命令，可以让 AI 帮你完成一些事情。用户只需要在 Chat 的时候输入 ‎⁠/command⁠ 就可以快速通过预置的 prompt 进行交互</li>
<li>网络搜索+AI 能力融合，检索信息非常方便</li>
<li>直接在地址栏或侧边栏输入问题，AI 可基于当前网页进行内容总结、对比、写作、代码生成等</li>
<li>用户可用自然语言描述需求，让 AI 帮你创建“Skills”——比如一键切换阅读模式、自动填写表单、批量整理标签页等</li>
<li>在任意文本框内，AI 可充当写作助手，帮你润色、改写、翻译、生成摘要，甚至模仿你的写作风格</li>
</ul>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/5fff0afe612d6fa60c10fae36610b678.png" alt="大屏更爽"></p>
<p>但也存在一些问题：</p>
<ul>
<li>浏览器插件的 Side Panel 不可用，估计是开发团队将 Side Panel 彻底改为了核心的 AI Chat 功能</li>
<li>无法保留上次打开的页面，当浏览器退出之后页面将会全部关闭，仅能从历史记录中逐个打开，非常不方便。但好消息是，官方在社媒上回复用户这个功能将会很快支持。</li>
</ul>
<h2>最后</h2>
<p>嘿嘿，最后在这里分享一下我的邀请码，官方限制仅可邀请三人，先到先得：</p>
<p><a href="https://diabrowser.com/invite/T0DD8A">https://diabrowser.com/invite/T0DD8A</a></p>
<p>啊，对了！windows 用户暂时没法用 Dia...</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/0e9b438c3b5f8d8ee2fb5226c4dd21a2.png" alt="image.png"></p>
<p>最后，欢迎各位点赞和转发支持！Bye！</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[技术分享：通过 URL 传递数据遇到的问题]]></title>
            <link>https://i5lin.top/blog/post/3160833591</link>
            <guid isPermaLink="false">3160833591</guid>
            <pubDate>Thu, 19 Jun 2025 16:15:20 GMT</pubDate>
            <description><![CDATA[今天是 2025 年 06 月 19 日，下班之前给大家分享一下我在项目里通过 URL 传递数据遇到的一些问题。]]></description>
            <content:encoded><![CDATA[<p>今天是 2025 年 06 月 19 日，下班之前给大家分享一下我在项目里通过 URL 传递数据遇到的一些问题。</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/bb9a139acb5611d4647f5c12230bedeb.png" alt="image.png"></p>
<h2>场景</h2>
<p>我们有一个后台系统，产品经理提出一个需求：在当前系统内部调用另一个系统的一些服务。</p>
<p>两个系统之间的代码虽然可以通用，但之前并没有做 Monorepo 之类的规划，因此我们决定通过 iframe 引入另一个系统的服务。</p>
<h2>实现</h2>
<blockquote>
<p>细节方面不再赘述 🤣</p>
</blockquote>
<p>在常规引入 iframe 之前，我对目标项目做了修改，新增了一个无鉴权的路由，通过 URL 接收当前系统传来的基础数据和鉴权 token，然后用 token 换取目标系统的 JWT 数据，再重定向到最终的路由。</p>
<h2>问题不断出现</h2>
<p>整个流程非常简单，直到今天测试同事找到我说页面打开报错了。</p>
<p>起因很简单，后端提供了一个超大的 JSON 数据，我编码后通过 URL 传递给目标系统的 Next.js 服务进行处理，导致 Next.js 返回了 431 状态码（Request Header Fields Too Large）。这可能是 URL 过长导致的问题。</p>
<p>我首先想到，JSON 数据序列化为字符串后，经 <code>encodeURIComponent()</code> 编码可能过长。此前数据序列化后长度正常。</p>
<blockquote>
<p><code>encodeURIComponent()</code> 编码可确保 URL 格式合法，防止查询分隔符破坏结构，处理非 ASCII 字符，并提升跨平台兼容性。</p>
</blockquote>
<p>于是我灵光一闪，想到此前处理过一个场景：通过 <code>btoa()</code> 将字符串转为 Base64 字符串，其长度通常比 <code>encodeURIComponent()</code> 编码的结果短。于是，我尝试通过 Base64 编码超大数据并传递。</p>
<blockquote>
<p><code>btoa()</code>（&quot;binary to ASCII&quot;）是浏览器提供的内置方法，用于将二进制字符串编码为 Base64。</p>
</blockquote>
<p>可惜我忽略了一点：<code>btoa()</code> 方法仅支持 Latin1 范围内的字符，无法处理中文、emoji 或特殊符号。</p>
<p>结果我犯蠢了，很显然：Base64 编码后字符串很长，甚至比 <code>encodeURIComponent()</code> 处理后更长。</p>
<p>于是我立即引入了 <a href="https://github.com/pieroxy/lz-string">pieroxy/lz-string</a> 库来压缩 Base64 字符串！</p>
<blockquote>
<p><a href="https://github.com/pieroxy/lz-string">pieroxy/lz-string: LZ-based compression algorithm for JavaScript</a>：压缩 Base64 字符串的利器，常用于 localStorage 存储或将图片解压为 Base64 嵌入 HTML 等场景。</p>
</blockquote>
<p>🚀 不错，Base64 字符串压缩后变小了，<code>lz-string</code> 还能自动处理 Latin1 范围外的字符，省心省力！</p>
<p>但是...</p>
<p>Base64 字符串可能包含 URL 中的分隔字符。为确保数据完整性，仍需通过 <code>encodeURIComponent()</code> 进行编码。</p>
<p>通过 URL 传输超大 JSON 数据就像一条岔路，也许我们一开始就不应选择这种方式。</p>
<blockquote>
<p>RESTful API 规范建议开发者通过 GET 请求 API 接口，通常通过 URL 传递参数实现，但不适合数据量大的场景。浏览器、后端、网关等都可能破坏 URL 中数据的完整性。</p>
</blockquote>
<h2>换一条路</h2>
<p>通过 Channel Messaging API 机制，在父窗口和 iframe 之间建立安全的双向通信信道。</p>
<blockquote>
<p>如果你想了解原生 Channel Messaging API 的通信机制，可查看 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API">Channel Messaging API - Web APIs | MDN</a>。</p>
</blockquote>
<p>如果是一年前，我可能会自己封装代码实现所需功能，打造一把“锤子”。</p>
<p>但现在，我直接使用了 <a href="https://github.com/Aaronius/penpal">Aaronius/penpal</a> 库，它基于 postMessage 提供简化的通信功能。</p>
<p>让我们花几分钟看看示例，在父窗口创建 iframe 和 messenger 实例来连接子窗口：</p>
<pre><code class="language-javascript">import { WindowMessenger, connect } from &#39;penpal&#39;;

const iframe = document.createElement(&#39;iframe&#39;);
iframe.src = &#39;https://childorigin.example.com/path/to/iframe.html&#39;;
document.body.appendChild(iframe);

const messenger = new WindowMessenger({
  remoteWindow: iframe.contentWindow,
  // Defaults to the current origin.
  allowedOrigins: [&#39;https://childorigin.example.com&#39;],
  // Alternatively,
  // allowedOrigins: [new URL(iframe.src).origin]
});

const connection = connect({
  messenger,
  // Methods the parent window is exposing to the iframe window.
  methods: {
    add(num1, num2) {
      return num1 + num2;
    },
  },
});

const remote = await connection.promise;
// Calling a remote method will always return a promise.
const multiplicationResult = await remote.multiply(2, 6);
console.log(multiplicationResult); // 12
const divisionResult = await remote.divide(12, 4);
console.log(divisionResult); // 3
</code></pre>
<p>在子窗口也创建 messenger 来连接父窗口：</p>
<pre><code class="language-javascript">import { WindowMessenger, connect } from &#39;penpal&#39;;

const messenger = new WindowMessenger({
  remoteWindow: window.parent,
  // Defaults to the current origin.
  allowedOrigins: [&#39;https://parentorigin.example.com&#39;],
});

const connection = connect({
  messenger,
  // Methods the iframe window is exposing to the parent window.
  methods: {
    multiply(num1, num2) {
      return num1 * num2;
    },
    divide(num1, num2) {
      // Return a promise if asynchronous processing is needed.
      return new Promise((resolve) =&gt; {
        setTimeout(() =&gt; {
          resolve(num1 / num2);
        }, 1000);
      });
    },
  },
});

const remote = await connection.promise;
// Calling a remote method will always return a promise.
const additionResult = await remote.add(2, 6);
console.log(additionResult); // 8
</code></pre>
<p>配置明确的 <code>allowedOrigins</code> 后，消息传递具有较高的安全性。</p>
<p>此外，<code>penpal</code> 还适用于 Web Worker 之间的通信，尤其适合超大数据传输，能节省大量时间以专注于更有意义的工作。</p>
<p>注意：任何时候都可通过调用 <code>connection.destroy()</code> 销毁连接，以移除事件监听器并正确进行垃圾回收。</p>
<h2>最后</h2>
<p>准备下班，经验总是在不经意间积累起来的，下次再见。</p>
<p>当然，期待大家都点赞、转发和支持！</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[六月谈：ECMAScript News 分享]]></title>
            <link>https://i5lin.top/blog/post/3157328098</link>
            <guid isPermaLink="false">3157328098</guid>
            <pubDate>Wed, 18 Jun 2025 15:31:19 GMT</pubDate>
            <description><![CDATA[hello，大家好！今天笔者的邮箱收到了 ECMAScript News 的邮件，看到了一些不错的新讯息，于是花了点时间写下这篇短文，给大家分享一下！]]></description>
            <content:encoded><![CDATA[<p><strong>hello，大家好！今天笔者的邮箱收到了 ECMAScript News 的邮件，看到了一些不错的新讯息，于是花了点时间写下这篇短文，给大家分享一下！</strong></p>
<h2>Exploring JavaScript ES2025</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/10190a2dd657a5a692c483ee71def0c2.png" alt="image.png"> <strong>著名工程师 Dr.Axel 发布了最新版的免费电子书《Exploring JavaScript ES2025》，作者自述：****“探索 JavaScript” 通过提供尽可能一致的现代视角，使得该语言对新手来说更加容易学习。</strong></p>
<p><strong>无论你是新手小白，还是一个资深的 JavaScript 开发者，这本书都可以让你了解到最新的 JavaScript 知识，以及作者分享的部分****测试驱动</strong>的编程练习，最重要的是：免费！</p>
<p><strong>你可以免费阅读在线版：</strong><a href="https://exploringjs.com/js/book/index.html">Table of contents • Exploring JavaScript (ES2025 Edition)</a></p>
<h2>Shopify 团队推动浏览器 Import Maps 支持</h2>
<p><strong>Shopify 团队积极适配了现代浏览器的 Import Maps 支持。文章提到，Import Maps 在 Chrome（版本 133 起）和 Safari（版本 18.4 起）等浏览器中可用。他们通过 Polyfills 或其他兼容性方案，确保在不支持 Import Maps 的旧版浏览器中仍能正常运行。</strong></p>
<blockquote>
<p><strong>Import Maps 是浏览器原生支持的一种 JavaScript 功能，用于控制和映射 ECMAScript 模块（ESM）的导入路径。它通过一个 JSON 格式的配置（通常嵌入在 HTML 的 <strong><script type="importmap"></strong> 标签中）</strong></p>
</blockquote>
<p><strong>让我们来看一个语法例子：</strong></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/2d55e111dbff3b7004706256ce4db75b.png" alt="image.png"></p>
<ul>
<li><strong>imports</strong>：定义模块标识符到 URL 的映射。例如，<code>import myModule from &#39;my-module&#39;</code> 会解析到指定的 CDN 地址。</li>
<li><strong>scopes</strong>（可选）：为特定路径范围提供不同的模块映射，允许更细粒度的控制。</li>
</ul>
<p><strong>开发者可以直接用简洁的模块名代替长 URL 或者相对路径。重点在于减少打包工具依赖，可以在浏览器中直接利用原生能力解析模块。</strong></p>
<h2>Nodejs v24.2.0</h2>
<p><strong>2025 年 6 月 9 日，Nodejs 发布了最新的 24.2.0 版本，引入了</strong> <code>import.meta.main</code>允许开发者轻松判断当前脚本是作为主模块直接运行（例如，通过 node script.js 执行）还是作为模块被导入。</p>
<p><strong>与 Import Maps 的上下文相关：import.meta.main 增强了 ESM 的模块化开发体验，特别是在使用 Import Maps 时，开发者可以通过它更精确地控制模块的行为（例如，仅在主模块执行初始化逻辑），提高代码可读性和跨环境兼容性，尤其在结合 Import Maps 的动态模块加载场景中。</strong></p>
<h2>测试工具相关</h2>
<h3>Jest 30：增强 ESM 的支持</h3>
<p><strong>Jest 30 是 Jest 测试框架的一次重大更新，发布于 2025 年 6 月 4 日。本次更新带来了显著的性能提升、功能改进和对现代 JavaScript 生态系统的更好支持。以下是对 Jest 30 更新的详细分析，基于官方博客内容和其他相关信息：</strong></p>
<ol>
<li><strong>性能优化</strong></li>
</ol>
<ul>
<li><strong>测试速度提升：Jest 30 平均将测试运行时间缩短了 37%。这是通过优化内部架构和减少内存使用量实现的，特别是在大型项目中表现明显。</strong></li>
<li><strong>内存使用量减少</strong>：新版本对内存管理进行了改进，使得在运行大量测试用例时，内存占用更低，适合资源受限的环境。</li>
<li><strong>冷启动时间优化</strong>：通过改进编译器和模块加载机制，Jest 30 的启动时间显著减少，提升了开发者的工作效率。</li>
</ul>
<ol start="2">
<li><strong>功能更新与新增特性</strong></li>
</ol>
<ul>
<li><strong>expect.arrayOf</strong>：新增了 expect.arrayOf 方法，允许开发者更方便地验证数组元素是否符合特定类型或结构，简化了常见的测试场景。</li>
<li><strong>测试重试机制</strong>：Jest 30 引入了内置的测试重试功能，允许在测试失败时自动重试若干次，特别适合处理不稳定的测试（如涉及网络请求的测试）。</li>
<li><strong>TypeScript 5.4+ 支持：Jest 30 更新了对 TypeScript 的支持，最低要求 TypeScript 5.4，确保与最新 TypeScript 特性的兼容性。</strong></li>
<li><strong>jsdom 26 升级：Jest 默认的 DOM 模拟环境升级到 jsdom 26，提供更现代的浏览器 API 支持，增强了对前端测试的兼容性。</strong></li>
<li><strong>Node.js 版本支持调整：停止支持 Node.js 14、16、19 和 21，推荐使用 Node.js 18 或更高版本，确保与现代 Node.js 生态系统保持一致。</strong></li>
</ul>
<ol start="3">
<li><strong>精简与现代化</strong></li>
</ol>
<ul>
<li><strong>移除 expect 的别名方法：Jest 30 删除了部分冗余的 expect 别名方法（如 toBeCalled 等），以简化 API 并减少维护负担。开发者需更新代码以使用标准方法（如 toHaveBeenCalled）。</strong></li>
<li><strong>零配置体验改进</strong>：Jest 继续优化其零配置特性，进一步降低了初学者的入门门槛，同时保持对高级用户的灵活性。</li>
<li><strong>更频繁的发布计划：官方承认 Jest 29 到 Jest 30 的三年间隔过长，承诺未来将加快重大版本发布频率，以更好地适应 JavaScript 生态的快速发展。</strong>
<img src="https://imgs.search.brave.com/HQeBtxQFVfpGiEV6o9oDP93KboUwytKlQeJIQ55GvyE/rs:fit:32:32:1:0/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvNzE5NGY5NjFh/ZmRlNDQ3OWI2ZTAz/MTMyYjdlNjJiOTJh/NmM2M2RjNmIyMDMz/NzA1ZDM3NjEyMmFl/OTQ5ODliYi9qZXN0/anMuaW8v" alt="img"></li>
</ul>
<h3>Rstest</h3>
<p><img src="https://assets.rspack.rs/rstest/rstest-logo.svg" alt="logo"></p>
<blockquote>
<p><a href="https://rstest.rs/zh/?utm_source=ECMAScript.news&utm_medium=Weekly+Newsletter&utm_campaign=2025-06-18">Rstest - 基于 Rspack 的测试框架</a></p>
</blockquote>
<p><strong>由 Rspack 驱动的测试框架，为 Rspack 生态提供全面、一流的支持！围绕 Rspack 打造的 JavaScript 统一工具链，具有优秀的性能和一致的架构。</strong></p>
<p><strong>如果你的项目使用的是 RSpack ，那么这个测试框架绝对值得了解一下。</strong></p>
<h2>Linting 工具</h2>
<h3>Oxlint V1.0 - 2026/06/19</h3>
<p><img src="https://cdn.jsdelivr.net/gh/oxc-project/oxc-assets/round-bubbles.png" alt="img"></p>
<blockquote>
<p><a href="https://voidzero.dev/posts/announcing-oxlint-1-stable?utm_source=ECMAScript.news&utm_medium=Weekly+Newsletter&utm_campaign=2025-06-18">Announcing Oxlint 1.0 | VoidZero</a>:</p>
<p><strong>Oxlint</strong>是一款由<strong>Rust</strong>驱动的<strong>JavaScript</strong>和<strong>TypeScript</strong>代码检查工具，旨在快速且易于集成。自2023年12月的首次发布以来，<strong>Oxlint</strong>经历了重大改进，现已发布第一版稳定版 1.0。除了稳定版发布，我们还想宣布，<strong>Oxlint</strong>有一位全职维护者<strong>Cameron</strong>和一支不断壮大的核心团队，致力于维护和提升这个代码检查工具。</p>
</blockquote>
<p><strong>Oxlint 的野心是构建一个统一的、高性能的 JavaScript 工具链，包括解析器、转换器、解析器、格式化器、压缩器、打包器和测试运行器等，在去年六月九日的时候发布了第一个稳定版。</strong></p>
<p><strong>如果你对处理不同的前端工具感到厌烦，那么或许可以试试 Oxlint 这样的瑞士军刀。Oxlint 会成功吗？亦或像 Roma.js 一样逐渐被开发者淡忘。</strong></p>
<blockquote>
<p><strong>6 月 12 日，Oxlint 立马发布了 v1.1.0 版本，看起来进展不错。</strong></p>
</blockquote>
<p><strong>等会，如果你不喜欢尤雨溪，那么或许可以去支持隔壁的 Biome.js~</strong></p>
<p><img src="https://biomejs.dev/_astro/slogan-light-transparent.CUBRXROI_A0IIg.svg" alt="Biome, Toolchain of the web"></p>
<p><strong>传送门：</strong><a href="https://biomejs.dev/">Biome, toolchain of the web</a></p>
<h3>ESlint</h3>
<p><strong>文章：</strong></p>
<ul>
<li><a href="https://eslint.org/blog/2025/06/language-plugins-dsl-benefits/?utm_source=ECMAScript.news&utm_medium=Weekly+Newsletter&utm_campaign=2025-06-18">How ESLint language plugins enhance DSL usability - ESLint - Pluggable JavaScript Linter</a>（解锁您的领域特定语言的全部潜能，通过无缝集成ESLint插件来提升一致性、可用性和开发者采用率。）</li>
<li><a href="https://eslint.org/blog/2025/06/eslint-v9.29.0-released/?utm_source=ECMAScript.news&utm_medium=Weekly+Newsletter&utm_campaign=2025-06-18">ESLint v9.29.0 released - ESLint - Pluggable JavaScript Linter</a> 添加对显式资源管理器的 ESLint 支持及其其他改进</li>
</ul>
<h2>最后</h2>
<p><strong>晚安。</strong></p>
<p><strong>希望每天都能更新，欢迎点赞、留言 😄</strong></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[从入门到放弃再到成功：一个前端程序员的后端 API 调试血泪史]]></title>
            <link>https://i5lin.top/blog/post/3134437626</link>
            <guid isPermaLink="false">3134437626</guid>
            <pubDate>Tue, 10 Jun 2025 17:56:44 GMT</pubDate>
            <description><![CDATA[这是一个真实的技术调试故事，记录了一个主要从事前端开发的程序员，在 AI 助手的帮助下，如何一步步解决 Chrome 扩展后端 API 的集成测试问题。从最初的 18/31 测试失败，到最终的 31/31 全部通过，这个过程充满了挫折、学习和成长。]]></description>
            <content:encoded><![CDATA[<p><img src="https://de4965e.webp.li/blog-images/2025/06/83838f05b265bb0bac21600397a3a118.jpeg" alt=""></p>
<p>从入门到放弃再到成功：一个前端程序员的后端 API 调试血泪史</p>
<blockquote>
<p><strong>序言</strong>：这是一个真实的技术调试故事，记录了一个主要从事前端开发的程序员，在 AI 助手的帮助下，如何一步步解决 Chrome 扩展后端 API 的集成测试问题。从最初的 18/31 测试失败，到最终的 31/31 全部通过，这个过程充满了挫折、学习和成长。</p>
</blockquote>
<hr>
<h2>第一章：问题的开始 - &quot;为什么手动测试正常，自动化测试全挂？&quot;</h2>
<p>我叫小李，是一名有着3年经验的前端开发工程师。最近在开发一个基于 Plasmo 框架的 Chrome 扩展，前端部分我驾轻就熟，但后端 API 对我来说还是个相对陌生的领域。</p>
<p>项目采用了比较现代的技术栈：</p>
<ul>
<li><strong>后端</strong>: Hono + TypeScript + Zod + Cloudflare Workers</li>
<li><strong>前端</strong>: React + TypeScript + Tailwind CSS</li>
<li><strong>测试</strong>: Vitest 集成测试</li>
</ul>
<p>一切看起来都很美好，直到我开始写集成测试...</p>
<h3>困惑的开始</h3>
<p>那是一个周五的下午，我刚写完用户认证相关的 API 接口。手动测试一切正常：</p>
<ul>
<li>用户注册 ✅</li>
<li>用户登录 ✅</li>
<li>获取用户信息 ✅</li>
<li>更新用户设置 ✅</li>
</ul>
<p>我满心欢喜地跑了一下集成测试，结果...</p>
<pre><code class="language-bash">❯ npm run test:integration

 FAIL  Tests failed: 18/31
</code></pre>
<p>**18个测试失败！**我当场懵了。手动用 Postman 测试明明都正常，为什么自动化测试全挂了？</p>
<p>最让我困惑的是错误信息：</p>
<pre><code>Expected status 201, got 400. Response: {&quot;success&quot;:false,&quot;message&quot;:&quot;用户名为必需字段&quot;,&quot;status&quot;:400}
</code></pre>
<p>用户名是必需字段？但是我明明在测试中生成了用户名啊！我检查了测试代码：</p>
<pre><code class="language-typescript">const testUser = generateTestUser()
console.log(&#39;Generated user:&#39;, testUser) // { username: &#39;test_123&#39;, email: &#39;test@example.com&#39;, password: &#39;password123&#39; }

const response = await client.register(testUser)
</code></pre>
<p>数据明明都有，为什么服务器收到的是 undefined？</p>
<h3>求助 AI：第一次对话</h3>
<p>绝望之中，我决定向 AI 求助。我详细描述了问题：</p>
<blockquote>
<p>&quot;我的 Chrome 扩展 API 集成测试失败了，18/31 个测试不通过。奇怪的是，手动测试都正常，但自动化测试时服务器总是收到 undefined 的字段值。使用的是 Hono + TypeScript + Zod + Vitest，你能帮我分析一下可能的原因吗？&quot;</p>
</blockquote>
<p>AI 的回答让我眼前一亮：</p>
<blockquote>
<p>&quot;这个问题很可能是测试环境配置问题。在 Node.js 环境中运行测试时，可能缺少 fetch polyfill，或者请求头配置有问题。让我们先检查 Vitest 配置...&quot;</p>
</blockquote>
<hr>
<h2>第二章：环境配置的坑 - &quot;Node.js 没有原生 fetch？&quot;</h2>
<h3>第一个发现：缺少 fetch polyfill</h3>
<p>AI 指导我检查 <code>vitest.config.ts</code> 文件，我发现确实没有配置 setupFiles：</p>
<pre><code class="language-typescript">// vitest.config.ts - 修复前
export default defineConfig({
  test: {
    environment: &#39;node&#39;,
    // 缺少 setupFiles 配置！
  }
})
</code></pre>
<p>原来 Node.js 环境下需要手动引入 fetch polyfill！AI 建议我：</p>
<ol>
<li>安装 <code>undici</code> 包</li>
<li>创建 <code>tests/setup.ts</code> 文件</li>
<li>配置全局 fetch</li>
</ol>
<pre><code class="language-bash">npm install --save-dev undici
</code></pre>
<pre><code class="language-typescript">// tests/setup.ts
import { fetch, Headers, Request, Response } from &#39;undici&#39;;

global.fetch = fetch as any;
global.Headers = Headers as any;
global.Request = Request as any;
global.Response = Response as any;
</code></pre>
<pre><code class="language-typescript">// vitest.config.ts - 修复后
export default defineConfig({
  test: {
    environment: &#39;node&#39;,
    setupFiles: [&#39;./tests/setup.ts&#39;]  // 添加这行
  }
})
</code></pre>
<p>我兴奋地重新运行测试，结果...还是失败！但这次错误信息变了：</p>
<pre><code>TypeError: Cannot read properties of undefined (reading &#39;headers&#39;)
</code></pre>
<p>这说明至少 fetch 调用生效了，但还有其他问题。</p>
<h3>第二个发现：请求头配置问题</h3>
<p>AI 继续帮我分析，发现了 API 客户端中的一个细微但致命的问题：</p>
<pre><code class="language-typescript">// api-client.ts - 有问题的版本
async request(endpoint: string, options: RequestOptions = {}) {
  const config: RequestInit = {
    method: options.method || &#39;GET&#39;,
    ...options.headers,  // 这里有问题！当 headers 为 undefined 时会出错
    body: options.body ? JSON.stringify(options.body) : undefined,
  }
}
</code></pre>
<p>AI 解释说：&quot;当 <code>options.headers</code> 为 undefined 时，<code>...undefined</code> 会导致问题。应该使用 <code>...(options.headers || {})</code> 来确保始终展开一个对象。&quot;</p>
<p>修复后的代码：</p>
<pre><code class="language-typescript">// api-client.ts - 修复后
async request(endpoint: string, options: RequestOptions = {}) {
  const config: RequestInit = {
    method: options.method || &#39;GET&#39;,
    body: options.body ? JSON.stringify(options.body) : undefined,
    headers: {
      &#39;Content-Type&#39;: &#39;application/json&#39;,
      ...(options.headers || {})  // 修复：确保 headers 不为 undefined
    }
  }
}
</code></pre>
<p>再次运行测试，这次有了重大突破！很多测试开始返回 201 Created 状态码，说明请求终于能正确发送到服务器了。</p>
<p>但是，新的问题出现了...</p>
<hr>
<h2>第三章：Schema 验证的噩梦 - &quot;数据结构不匹配&quot;</h2>
<h3>问题转移：从请求到响应</h3>
<p>虽然请求问题解决了，但现在测试失败的原因变成了响应验证问题：</p>
<pre><code>ZodError: [
  {
    &quot;code&quot;: &quot;invalid_type&quot;,
    &quot;expected&quot;: &quot;object&quot;,
    &quot;received&quot;: &quot;undefined&quot;,
    &quot;path&quot;: [&quot;message&quot;],
    &quot;message&quot;: &quot;Required&quot;
  }
]
</code></pre>
<p>AI 帮我分析：&quot;现在问题是响应结构与测试预期不匹配。服务器返回的数据缺少某些字段，或者字段结构与 Zod schema 定义不一致。&quot;</p>
<p>我开始逐个检查 API 响应：</p>
<h3><code>/auth/me</code> 接口的问题</h3>
<p>测试期望的响应结构：</p>
<pre><code class="language-typescript">{
  success: true,
  message: string,
  data: {
    user: UserObject
  }
}
</code></pre>
<p>但实际的服务器响应：</p>
<pre><code class="language-typescript">{
  success: true,
  data: UserObject,  // 缺少包装对象
  timestamp: &quot;2024-01-01T00:00:00.000Z&quot;
  // 缺少 message 字段
}
</code></pre>
<p>AI 指导我修复服务器端代码：</p>
<pre><code class="language-typescript">// 修复前
return c.json({
  success: true,
  data: user,
  timestamp: new Date().toISOString()
})

// 修复后
return c.json({
  success: true,
  data: {
    user  // 包装在对象中
  },
  message: &quot;获取用户信息成功&quot;,  // 添加 message
  timestamp: new Date().toISOString()
})
</code></pre>
<h3>可用性检查接口的问题</h3>
<p>类似的问题也出现在用户名和邮箱可用性检查接口上。AI 建议我统一添加 message 字段：</p>
<pre><code class="language-typescript">// 用户名可用性检查 - 修复后
return c.json({
  success: true,
  data: {
    username,
    available: !exists
  },
  message: &quot;用户名可用性检查完成&quot;,  // 新增
  timestamp: new Date().toISOString()
})
</code></pre>
<p>但修复这些之后，还有一个更复杂的问题等着我...</p>
<hr>
<h2>第四章：业务逻辑的挑战 - &quot;receiveOfficialMessages 到底放哪里？&quot;</h2>
<h3>意外的需求理解错误</h3>
<p>在修复测试的过程中，AI 发现了一个我在需求理解上的错误。我把 <code>receiveOfficialMessages</code> 当作了一个纯 UI 设置，但实际上它应该是：</p>
<ol>
<li><strong>用户表字段</strong>：存储在数据库用户表中</li>
<li><strong>注册时可设置</strong>：用户注册时可以选择是否接收官方消息</li>
<li><strong>设置中可修改</strong>：后续可以在设置页面中修改</li>
</ol>
<p>AI 耐心地为我解释了这个概念：</p>
<blockquote>
<p>&quot;在实际应用中，<code>receiveOfficialMessages</code> 这类字段通常既是用户属性，也是用户设置。它需要存储在用户表中，因为它影响系统行为（比如是否发送邮件通知），而不仅仅是 UI 偏好设置。&quot;</p>
</blockquote>
<h3>数据库 Schema 调整</h3>
<p>首先需要修改用户 Schema：</p>
<pre><code class="language-typescript">// user.ts - 修复前
export const UserSchema = z.object({
  id: z.string().uuid(),
  username: z.string(),
  email: z.string().email(),
  isSponsored: z.boolean(),
  settings: UserSettingsSchema,  // receiveOfficialMessages 错误地放在这里
  createdAt: z.string(),
  updatedAt: z.string()
})

// user.ts - 修复后
export const UserSchema = z.object({
  id: z.string().uuid(),
  username: z.string(),
  email: z.string().email(),
  isSponsored: z.boolean(),
  receiveOfficialMessages: z.boolean(),  // 提升到用户级别
  settings: UserSettingsSchema,
  createdAt: z.string(),
  updatedAt: z.string()
})
</code></pre>
<h3>服务器逻辑的复杂性</h3>
<p>最复杂的部分是修改 <code>updateUserSettings</code> 方法。AI 指导我实现了一个动态 SQL 更新的方案：</p>
<pre><code class="language-typescript">// AuthService.updateUserSettings - 修复后
async updateUserSettings(userId: string, settings: any) {
  // 分离用户级设置和UI设置
  const { receiveOfficialMessages, ...uiSettings } = settings
  
  // 动态构建查询
  let updateQuery = &quot;UPDATE users SET settings = ?, updated_at = ?&quot;
  const params = [JSON.stringify(uiSettings), new Date().toISOString()]
  
  // 如果包含用户级设置，添加到查询中
  if (receiveOfficialMessages !== undefined) {
    updateQuery += &quot;, receive_official_messages = ?&quot;
    params.push(receiveOfficialMessages)
  }
  
  updateQuery += &quot; WHERE id = ?&quot;
  params.push(userId)
  
  await this.db.prepare(updateQuery).bind(...params).run()
}
</code></pre>
<p>AI 解释说：&quot;这种方法的好处是，可以灵活地更新不同类型的设置，而不需要写很多重复的代码。&quot;</p>
<hr>
<h2>第五章：细节决定成败 - &quot;魔鬼在细节中&quot;</h2>
<h3>错误消息的小问题</h3>
<p>经过前面的大修改，测试通过率已经从 18/31 提升到了 30/31，只剩下最后一个测试失败：</p>
<pre><code>Expected error message to contain &quot;token&quot;, but got &quot;无效的认证令牌&quot;
</code></pre>
<p>AI 指出这是一个简单的字符串匹配问题：</p>
<pre><code class="language-typescript">// 修复前
throw new HTTPException(401, { message: &quot;无效的认证令牌&quot; })

// 修复后  
throw new HTTPException(401, { message: &quot;无效的token&quot; })
</code></pre>
<p>看起来微不足道，却是自动化测试的关键点。AI 说：&quot;在编写测试时，错误消息的一致性很重要。这不仅仅是为了通过测试，也是为了给前端提供一致的用户体验。&quot;</p>
<h3>输入验证的完善</h3>
<p>还有一个问题是用户名和邮箱的格式验证。测试中生成的用户名 <code>available_${Date.now()}</code> 长度超过了20个字符的限制。</p>
<p>AI 建议我在服务器端添加完整的输入验证：</p>
<pre><code class="language-typescript">// 用户名验证
if (username.length &lt; 3 || username.length &gt; 20 || !/^[a-zA-Z0-9_]+$/.test(username)) {
  throw new HTTPException(400, { message: &quot;用户名格式不正确&quot; })
}

// 邮箱验证
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
  throw new HTTPException(400, { message: &quot;邮箱格式不正确&quot; })
}
</code></pre>
<p>同时修改测试中的用户名生成策略：</p>
<pre><code class="language-typescript">// 修复前：用户名太长
const newUsername = `available_${Date.now()}`

// 修复后：控制长度
const newUsername = `avail${Date.now().toString().slice(-8)}`
</code></pre>
<hr>
<h2>第六章：胜利的曙光 - &quot;31/31 全部通过！&quot;</h2>
<h3>最后的调试</h3>
<p>经过几轮修复，我再次运行测试：</p>
<pre><code class="language-bash">❯ npm run test:integration

 ✓ tests/integration/basic.test.ts (11) 334ms
 ✓ tests/integration/auth.test.ts (20) 647ms

 Test Files  2 passed (2)
      Tests  31 passed (31)
</code></pre>
<p><strong>31/31 全部通过！</strong></p>
<p>我激动得差点从椅子上跳起来。从最初的 18/31 失败，到现在的 31/31 全部通过，这个过程虽然充满挫折，但收获巨大。</p>
<h3>回顾整个调试过程</h3>
<p>AI 帮我总结了这次调试的关键收获：</p>
<h4><strong>技术层面</strong></h4>
<ol>
<li><strong>环境配置</strong>：Node.js 测试环境需要正确配置 fetch polyfill</li>
<li><strong>请求处理</strong>：注意 undefined 值的处理，特别是在对象展开时</li>
<li><strong>响应结构</strong>：API 响应结构必须与前端期望完全一致</li>
<li><strong>数据验证</strong>：服务器端输入验证是必需的</li>
<li><strong>业务逻辑</strong>：理解数据应该存储在哪里（用户表 vs 设置表）</li>
</ol>
<h4><strong>流程层面</strong></h4>
<ol>
<li><strong>手动测试 vs 自动化测试</strong>：两者测试的环境和方式不同，可能暴露不同的问题</li>
<li><strong>逐步调试</strong>：先解决环境问题，再解决逻辑问题</li>
<li><strong>错误信息解读</strong>：仔细分析错误信息，每一个细节都可能是线索</li>
<li><strong>Schema 一致性</strong>：前后端的数据结构定义必须保持一致</li>
</ol>
<hr>
<h2>第七章：经验总结 - &quot;从前端到全栈的思维转变&quot;</h2>
<h3>作为前端程序员的感悟</h3>
<p>这次经历让我深刻体会到了前端和后端开发的区别：</p>
<h4><strong>前端思维 vs 后端思维</strong></h4>
<p><strong>前端思维</strong>：</p>
<ul>
<li>关注用户界面和交互体验</li>
<li>数据通常是&quot;拿来即用&quot;</li>
<li>错误处理主要是 UI 反馈</li>
</ul>
<p><strong>后端思维</strong>：</p>
<ul>
<li>关注数据的完整性和安全性</li>
<li>需要考虑数据存储、验证、权限等</li>
<li>错误处理需要考虑各种边界情况</li>
</ul>
<p>AI 在这个过程中就像一个经验丰富的后端导师，不仅帮我解决具体问题，还帮我理解背后的原理。</p>
<h3>自动化测试的价值</h3>
<p>以前我觉得写测试很麻烦，手动测试不是挺好的吗？但这次经历让我认识到：</p>
<ol>
<li><strong>自动化测试能发现手动测试发现不了的问题</strong></li>
<li><strong>测试环境和生产环境的差异很重要</strong></li>
<li><strong>好的测试不仅验证功能，还能验证错误处理</strong></li>
<li><strong>测试是活文档，描述了 API 的预期行为</strong></li>
</ol>
<h3>AI 辅助开发的感受</h3>
<p>在这个过程中，AI 不仅仅是一个代码生成工具，更像是：</p>
<ul>
<li><strong>问题诊断专家</strong>：能从错误信息中快速定位问题</li>
<li><strong>架构顾问</strong>：帮我理解数据应该如何组织</li>
<li><strong>代码审查员</strong>：发现我代码中的细微错误</li>
<li><strong>学习导师</strong>：解释每个修复背后的原理</li>
</ul>
<p>但AI也不是万能的，它需要我提供准确的问题描述和上下文信息。</p>
<hr>
<h2>结语：成长的意义</h2>
<p>从周五下午的绝望，到周一早上的胜利，这个调试过程花了我整整一个周末。虽然累，但我收获了：</p>
<h3><strong>技术技能</strong></h3>
<ul>
<li>学会了配置 Node.js 测试环境</li>
<li>理解了 HTTP 请求在不同环境下的差异</li>
<li>掌握了 Zod schema 验证的使用</li>
<li>学会了动态 SQL 查询的构建</li>
</ul>
<h3><strong>调试思维</strong></h3>
<ul>
<li>从现象到本质的分析能力</li>
<li>系统性解决问题的方法</li>
<li>错误信息的解读技巧</li>
</ul>
<h3><strong>心态转变</strong></h3>
<ul>
<li>从害怕后端到愿意深入了解</li>
<li>从依赖手动测试到重视自动化</li>
<li>从单打独斗到善用 AI 工具</li>
</ul>
<p>最重要的是，我意识到<strong>学习是一个持续的过程</strong>。技术在快速发展，工具在不断更新，但解决问题的方法论是相通的：</p>
<ol>
<li><strong>准确描述问题</strong></li>
<li><strong>系统性分析原因</strong></li>
<li><strong>逐步验证假设</strong></li>
<li><strong>总结经验教训</strong></li>
</ol>
<p>现在，每当我遇到新的技术难题时，我都会想起这次调试经历。它告诉我：<strong>没有解决不了的问题，只有还没找到对的方法</strong>。</p>
<p>而有了AI这样的工具，我们不再是孤军奋战。它就像一个24小时在线的技术导师，帮助我们更快地学习和成长。</p>
<hr>
<p><strong>后记</strong>：如果你也是一名前端程序员，正在向全栈发展的路上，希望这个故事能给你一些启发。记住，每一个bug都是学习的机会，每一次调试都是成长的过程。</p>
<p><em>Keep coding, keep learning, keep growing! 🚀</em></p>
<hr>
<p><strong>项目信息</strong>：</p>
<ul>
<li><strong>技术栈</strong>：Hono + TypeScript + Zod + Vitest + Cloudflare Workers</li>
<li><strong>测试框架</strong>：Vitest 集成测试</li>
<li><strong>开发工具</strong>：AI 辅助编程</li>
<li><strong>最终结果</strong>：31/31 测试通过 (100% 成功率)</li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[六月谈：只要不停止，就是进步]]></title>
            <link>https://i5lin.top/blog/post/3129376077</link>
            <guid isPermaLink="false">3129376077</guid>
            <pubDate>Mon, 09 Jun 2025 07:03:46 GMT</pubDate>
            <description><![CDATA[周刊转月刊了，最近非常懈怠，不知道怎么样能赚钱，不过知识分享不会停止。]]></description>
            <content:encoded><![CDATA[<p>Hello，大家好！我 <del>胡汉三</del> 又回来了！趁着人在候机，准备给大家分享一下最近接触到的东西！</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/04/743830015f373f880effa582f132d441.jpg" alt="WechatIMG92657.jpg"></p>
<h2>Claw Cloud - 爪云</h2>
<p><a href="https://run.claw.cloud/">ClawCloud Run | Build, Deploy, Manage &amp; Run in Cloud-Native Platform</a> 是一个 Docker 服务部署平台，专注于简化开发部署的过程。
使用 GitHub 注册，如果你的 GitHub 是在半年前注册的，那么平台每个月赠送 5 美元额度可用于部署 Docker 服务。</p>
<p>我用这个免费赠送的额度部署了我的密码管理器应用的接口服务，5 刀刚好够一个 1 Core、512 M 内存的用例。</p>
<p>如果你也想试试，欢迎使用我的邀请链接：<a href="https://console.run.claw.cloud/signin?link=BF40QG6YSZLW">传送门</a></p>
<blockquote>
<p><a href="https://console.run.claw.cloud/signin?link=BF40QG6YSZLW">https://console.run.claw.cloud/signin?link=BF40QG6YSZLW</a></p>
</blockquote>
<h2>ProNotes</h2>
<p><a href="https://www.pronotes.app/">ProNotes</a>是苹果 Notes 插件，能让您喜爱的笔记应用使用起来更加愉快。这个插件能够让你在使用 Notes 的时候轻松使用鼠标位置的浮动功能栏实现常规的格式操作，也能让你输入部分 <strong>markdown</strong> 语法的内容时自动转化格式为 notes 支持的样式。</p>
<p>如果你不熟悉 markdown 语法，那么也可以通过 <code>/</code> 关键字触发快捷功能进行输入。另外，还支持设置模版内容，快速输出一段模版文字。</p>
<p>此外，这个插件还有付费的 AI 功能，能够进行对话、编写、解释等等智能交互。</p>
<p>另外，这个插件正在开发 Vim 模式的功能，真不错 🤣。</p>
<h2>macIconChanger</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/04/cef63038ec2ef689a3bc876d2d6bd0ab.png" alt="image.png"></p>
<p><a href="https://github.com/Bengerthelorf/macIconChanger">Bengerthelorf/macIconChanger: Transform your Mac. One icon at a time.</a> 是我在 GitHub 上发现的一个基于 Swift 实现的 MacOS 应用图标修改程序，支持自定义系统上的其他 APP 的图标，与此同时这也是一个代码良好的的开源项目，很适合学习 Swift 开发的时候作为实战应用的参考项目。</p>
<h2>Flarum</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/04/b86bd2c3d37a9c18543706ae35d5f105.png" alt="image.png"></p>
<p><a href="https://flarum.org/">The extensible community framework - Flarum</a>是我在推上的一个独立开发者的帖子里看到的分享，这是一个开源的论坛软件，旨在为网站提供简单、快速且易用的在线社区讨论平台。</p>
<p>这位独立开发有一系列自己的付费和免费应用，因此经常需要和自己的用户沟通一些问题，简单使用社交媒体软件的体验不够好，因此他找到了这一个开源的社区论坛软件来处理自己的产品事务。</p>
<blockquote>
<p>支持在<a href="https://freeflarum.com/">FreeFlarum | Sign Up</a>快速试用</p>
</blockquote>
<p>这个产品的优点：</p>
<ul>
<li>在线式社区功能完整</li>
<li>移动端优化，UI 精美</li>
<li>支持完整的现代化功能：md 编辑器、灵活的权限管理、良好的浏览器和邮件通知机制。</li>
<li>开源免费</li>
</ul>
<p>总的来说，非常适合做为兴趣爱好者（如游戏、编程、摄影）创建讨论平台或者搭建客户支持论坛或内部员工交流平台。</p>
<h2>Github Hosts - Daily</h2>
<p><a href="https://github.com/maxiaof/github-hosts">maxiaof/github-hosts: 通过修改Hosts解决国内Github经常抽风访问不到,每日更新</a></p>
<p>每天吐槽一次国内的网络和云服务，真是恶心人。</p>
<p>如果你的 vps 是在大陆，那么一定会遇到 GitHub 无法解析的问题，那么可以考虑自己修改 Hosts 暂时规避一下。</p>
<h2>毫秒镜像</h2>
<p><a href="https://www.mliev.com/docs/1ms.run">毫秒镜像</a> 是一个提供 Docker 镜像服务提供商，只要注册其账号即可简单修改天朝服务器的 docker 镜像，免费和付费的功能差别如下：</p>
<table>
<thead>
<tr>
<th>免费</th>
<th>付费</th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>镜像获取</td>
<td>支持</td>
<td>支持</td>
</tr>
<tr>
<td>可用性</td>
<td>不承诺</td>
<td>保证</td>
</tr>
<tr>
<td>下载速度</td>
<td>不承诺</td>
<td>快</td>
</tr>
<tr>
<td>CDN</td>
<td>不承诺</td>
<td>就近节点</td>
</tr>
<tr>
<td>高峰期优先级</td>
<td>不承诺</td>
<td>保障高优先级</td>
</tr>
<tr>
<td>价格</td>
<td>免费</td>
<td>登录后可见</td>
</tr>
</tbody></table>
<p>基本是按流量来付费， 50G 12 个月内有效流量，价格为 6 RMB，仁者见仁智者见智吧。</p>
<p>同类提供者也可以看看：</p>
<ul>
<li><a href="https://0-docker.nat.tf/">Docker镜像加速服务说明</a></li>
<li><a href="https://docker.xuanyuan.me/">DockerHub镜像加速器 - 免费Docker镜像源国内加速 - DockerHub加速国内解决方案</a></li>
</ul>
<h2>每日必应壁纸</h2>
<p><a href="https://www.bingwallpaper.today/">Bing Wallpaper Today | Explore &amp; Download Daily Wallpapers in 4K</a> 这个每日必应壁纸网站做得真不错。</p>
<h2>Devin DeepWiki</h2>
<p><a href="https://docs.devin.ai/work-with-devin/deepwiki">DeepWiki - Devin Docs</a> 是发布了著名的 AI 程序员员工 Devin 所属的开发团队 Cognition Labs 最新发布的一个 AI 工具，旨在分析 github 上的公开仓库的代码，生成系统结构图，辅助用户阅读和理解代码内容和架构。用户可以通过自然语言提问，获取基于代码库内容的精准回答。</p>
<p>例如，我们在其官网上搜索开源库 ky （一个基于 fetch 的现代请求库），就能看到这个 AI 工具对整个项目的介绍和分析：</p>
<blockquote>
<p><a href="https://deepwiki.com/sindresorhus/ky">sindresorhus/ky | DeepWiki</a></p>
</blockquote>
<p><img src="https://de4965e.webp.li/blog-images/2025/05/dc15549a629a1b2d8b2fa9114a50e693.png" alt="image.png"></p>
<p>下面我问一个源代码的问题：
<img src="https://de4965e.webp.li/blog-images/2025/05/7b4ebe96e3ed0c643376a364e68040f4.png" alt="image.png"></p>
<p>这部分内容和我阅读源代码看到的一致，甚至告诉了我更多我未曾阅读到的内容和关键知识。
简直是杀手级的功能，我几乎忍不住立马寻找付费的入口。</p>
<p>阅读别人的代码绝对是一件辛苦的事情，不仅考验作者的功力，也考验读者自己的阅读能力和技术。</p>
<p>AI 的加持可以大大节约我们的时间，去做更有意义的事情。</p>
<h2>Favicon Generator</h2>
<p><a href="https://favicon.io/">The best Favicon Generator (completely free) - favicon.io</a> 是一个免费的站点 Favicon 生成工具，站长可以轻松上传一张 png 生成对应多平台的若干尺寸 Favicon，非常方便。</p>
<h2>Plate Rich Text Editor</h2>
<p><a href="https://platejs.org/">Build your rich-text editor - Plate</a> 是一个构件富文本编辑器的工具箱。</p>
<p>做过富文本相关需求的朋友肯定深有体会富文本功能的复杂，诸如光标管理、插件、兼容性等等需求都需要花很多心思处理。</p>
<p>如果你需要使用 React 技术栈开发富文本相关的功能，推荐了解一下 Plate.js 这个工具。 Plate.js 降低了富文本相关功能的复杂性，集成了诸多插件：</p>
<ul>
<li>段落</li>
<li>表格</li>
<li>图片</li>
<li>表格</li>
<li>AI 集成</li>
<li>SSR 支持</li>
</ul>
<blockquote>
<p>Plate.js 提供富文本的核心功能，自定义功能仅需要编写插件即可。</p>
</blockquote>
<p>另外，它基于 React，而且是“无头（Headless）”架构的。这意味着它只管核心逻辑，UI 部分完全由我们自己控制。你可以用任何你喜欢的 UI 库（比如他们推荐的 <code>shadcn/ui</code>），或者自己写样式，完全不会被它的默认样式限制住。</p>
<h2>immer</h2>
<p><a href="https://github.com/immerjs/use-immer">immerjs/use-immer: Use immer to drive state with a React hooks</a> 这是一个非常简洁且实用的 React Hooks 库，旨在通过简单的写法，简化修改深层次的 State 对象时的写法，减少展开运算符的使用。举个例子：</p>
<pre><code class="language-typescript">import { useImmer } from &#39;use-immer&#39;;

function MyComponent() {
  const [state, updateState] = useImmer({
    user: {
      name: &#39;Alice&#39;,
      details: {
        age: 30,
        city: &#39;New York&#39;
      }
    },
    items: [
      { id: 1, name: &#39;Item A&#39; },
      { id: 2, name: &#39;Item B&#39; }
    ]
  });

  const handleUpdate = () =&gt; {
    updateState(draft =&gt; {
      // 你可以直接修改 draft 对象，Immer 会在内部处理不可变更新
      draft.user.details.age += 1;
      draft.items.push({ id: 3, name: &#39;Item C&#39; });
    });
  };

  return (
    &lt;div&gt;
      &lt;p&gt;User Age: {state.user.details.age}&lt;/p&gt;
      &lt;button onClick={handleUpdate}&gt;Increment Age &amp; Add Item&lt;/button&gt;
      &lt;ul&gt;
        {state.items.map(item =&gt; (
          &lt;li key={item.id}&gt;{item.name}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>如果我们使用 <code>useState</code>创建这样的复杂对象，那么修改状态的时候相对来说要写的代码会更多，你不必写过多的展开运算符，也不用担心不小心修改了其他属性。</p>
<p>此外，Immer 使用数据共享来生成新的状态，相对手动处理创建新对象来说，性能更高。</p>
<p>当然，如果你只是使用简单的扁平数据，那么就没必要引入这个工具了。</p>
<h2>Upstash</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/3092c063a6c986c49f9857932013f0e4.png" alt="image.png"></p>
<p>Upstash 是一家提供无服务器云服务的公司， 旗下四大核心产品及其使用场景：</p>
<ul>
<li>Redis<ul>
<li>频繁访问的缓存数据快速响应</li>
<li>会话管理：在无服务器函数之间快速交换和管理会话数据</li>
<li>排行榜：支持 Redis sorted sets 数据，提供快速的排序列表数据集</li>
<li>速率限制：防止 DDOS 或滥用</li>
<li>实时计数和计量（用户数据统计、行为统计）</li>
</ul>
</li>
<li>Vector：AI 向量数据库系统<ul>
<li>语义搜索</li>
<li>检索增强生成</li>
<li>推荐引擎</li>
<li>图片、多媒体搜索</li>
</ul>
</li>
<li>QStash<ul>
<li>后台任务处理，避免主应用被任务阻塞</li>
<li>任务调度：支持按时触发的任务需求</li>
<li>延迟消息投递</li>
<li>可靠的 webhook，支持重试机制，提供 Dead letter queue 方便后续处理</li>
<li>扇出：将消息发送到不同的 http 节点</li>
</ul>
</li>
<li>Workflow<ul>
<li>需要长时间执行、可暂停等待外部事件的任务</li>
<li>复杂的任务编排</li>
<li>失败反馈</li>
</ul>
</li>
<li>Search Database<ul>
<li>基于元数据的数据库搜索服务</li>
</ul>
</li>
</ul>
<p>Upstash 提供了慷慨的免费额度让开发者使用，在产品的早期我们可以将工作交给成熟的第三方产品来减少工作量。</p>
<h2>Perplexity Pro</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/0f15ea2fab359d0f78e1c2cd7f14dbec.png" alt="image.png"></p>
<p><a href="https://www.perplexity.ai/">Perplexity</a></p>
<p>Perplexity 是一个由人工智能驱动的“答案引擎”和对话式搜索引擎，成立于2022年。它结合了大型语言模型（如GPT、Claude、Llama等）和实时互联网检索技术，能够理解用户用自然语言提出的问题，自动从网络和多种数据源中搜集、分析并生成简洁、准确且带有引用来源的答案。</p>
<p>据说是三星的商店泄露了一年的 Pro 优惠码：<strong>PPLXLIUMBLHOTVAJ2QI</strong></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/c16eb625a0340997bcad9b6b8b5c7d81.png" alt="image.png"></p>
<p>建议先将 IP 更换为美国或者新加坡，然后开无痕窗口，通过<a href="https://t.co/7WTf3tNLVe">https://perplexity.ai/join/p/redeem</a>这个链接进去，等待几分钟即可通过邮件获取验证码，然后输入优惠码领取（如果官网没有收回的话，大家就能体验体验一年的 Pro 服务）。</p>
<h2>Mousio - 软件模拟鼠标操作</h2>
<p><img src="https://wangchujiang.com/mousio/assets/screenshots-1.png" alt=""></p>
<p><a href="https://wangchujiang.com/mousio/">Mousio v1.6.0</a> 是独立开发者 jaywcjlove 的新作，可以在 Macos 上通过键盘控制鼠标移动和快速跳转、点击等等功能，算是没有鼠标或者蓝牙鼠标没电的时候的救星吧。</p>
<p><img src="https://wangchujiang.com/mousio/assets/screenshots-2.png" alt=""></p>
<p>某种意义上来说还是有用处的。</p>
<h2>Dartnode Free VPS</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/72f7746e81b078537d8d43dfb387203e.png" alt="image.png"></p>
<p><a href="https://dartnode.com/open-source">DartNode - Affordable Cloud Hosting, Dedicated Servers &amp; VPS Solutions</a> 提供的一个长期开源活动，开发者只需要在自己的 github 开源仓库下添加一个 dartnode 徽标，并且此仓库超过 1000 starts 就可以获得一个长期免费的 VPS，配置为：</p>
<ul>
<li><strong>IP地址</strong>：支持IPv4和IPv6（主要显示IPv4）。</li>
<li><strong>服务器配置</strong>：</li>
<li>操作系统：Ubuntu</li>
<li>CPU：1核（E5系列）</li>
<li>内存：1GB</li>
<li>存储：10GB</li>
<li>带宽：<strong>无限流量</strong>（需遵守可接受使用政策）</li>
<li><strong>服务器位置</strong>：美国休斯顿。</li>
</ul>
<p>以下是好事网友发布的国内延迟检测：
<img src="https://www.notion.so/image/attachment%3A298d6c08-a7e7-4804-80f4-a96646fefb57%3Aimage.png%3FspaceId%3Dd132450d-d072-405f-98f0-33b777956509?table=block&id=20a7d549-6f33-8072-9837-d4b21256e874&t=20a7d549-6f33-8072-9837-d4b21256e874" alt=""></p>
<h2>从 Idea 到产品</h2>
<p>Gumroad 的创始人<a href="https://x.com/shl">@shl</a>(Sahil Lavingia) 在 YouTube 上开了一门课，主要教大家怎么把一个 idea 变成一个具体的产品，可以看做是 The Minimalist Entrepreneur 的实践课。</p>
<p>传送门：<a href="https://www.youtube.com/watch?v=cfAXFeql9X8&list=PL_DfN-mKCGNsj-pn7PtnD-N3Ab4lAtywf">(69) How to come up with good business ideas - Lesson 1 - YouTube</a></p>
<p>总共 6 集：</p>
<ol>
<li>如何想到一个好的 idea</li>
<li>如何把 idea 变成一份商业计划</li>
<li>如何用 figma 打造原型</li>
<li>如何打造 MVP</li>
<li>如何 Pitch</li>
<li>. 如何增长</li>
</ol>
<h2>小火箭使用手册</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/161902c017f0477c39e2ac03d4bc249e.png" alt="image.png"></p>
<p><a href="https://lowertop.github.io/Shadowrocket/">Shadowrocket | 使用手册 补完计划</a> 是 github 上一个开源的小火箭网络工具使用手册，推荐给被复杂网络环境困住的每一个开发者朋友。</p>
<h2>给好奇者的 WebRTC</h2>
<blockquote>
<p><a href="https://webrtcforthecurious.com/zh/">介绍 | 给好奇者的WebRTC</a></p>
</blockquote>
<p>本书由 WebRTC 实现者创建，旨在与世界分享他们来之不易的知识。 <em>《给好奇者的 WebRTC》</em> 是一本针对那些一直在寻找更多知识的人编写的开源书。本书不止步于抽象层面。</p>
<p>本书全部是关于协议和 API 的，不会特别谈论任何软件。我们尝试总结 RFC，并将所有未记录的知识集中在一起。本书不是教程，也不会包含太多代码。</p>
<p>WebRTC 是一种优秀的技术，但它很难使用。本书与供应商无关，我们已尝试消除了各种利益冲突。</p>
<h2>LinuxMirrors</h2>
<p><a href="https://github.com/SuperManito/LinuxMirrors">SuperManito/LinuxMirrors: GNU/Linux 更换系统软件源脚本及 Docker 安装与换源脚本</a>,一键切换 Linux 默认源为国内镜像的脚本，支持 Debian、Ubuntu、CentOS、Raspberry Pi OS、Deepin 等主流发行版，并提供 Docker 安装脚本。</p>
<p>无数次吐槽国内的网络是地狱。</p>
<h2>Rive App</h2>
<blockquote>
<p><a href="https://rive.app/">Rive — a new way to design, build, and ship user interfaces</a></p>
</blockquote>
<p>Rive 是一款专为创建交互式动画和用户界面（UI）而设计的全新设计工具和平台。它集成了设计、动画、交互逻辑和跨平台运行时，为开发者和设计师提供了一整套从创作到发布的高效工作流程。</p>
<p>学习使用 Rive App，你可以创建跨平台可用的交互动画，并且 FPS 拉满。跟 Lottie 相比，Rive 的最大优势在于：</p>
<ul>
<li>支持复杂的交互和状态驱动动画，而不仅仅是线性播放</li>
<li>设计、动画、交互和运行时一体化，无需多工具切换，极大简化了工作流</li>
<li>跨平台表现一致，适合更广泛的应用场景，包括游戏引擎</li>
</ul>
<p>笔者看了看这个平台的介绍视频，官方提供了一系列的视频教程和博客教程，如果你想学习动画设计，那么这个平台或许可以了解一下。</p>
<h2>TypeScript Book</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/c0a5ee1ccf355f060a5aa313bf38d16a.png" alt="image.png"></p>
<p><a href="https://www.totaltypescript.com/books/total-typescript-essentials">Total TypeScript Essentials | Total TypeScript</a> 是 Matt Pocock 写的一本 TypeScript 知识电子书，完全免费，强推给使用 TypeScript 的开发者。</p>
<h2>图</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/6ff434b97a32a289bead71995351f94d.png" alt="image.png"></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/832f2cb5d896a05f24c753a1f90942b9.png" alt="image.png"></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/06/cb7118595be6c82bd1af9212f0813185.png" alt="image.png"></p>
<h2>最后</h2>
<p>SHIT.有人说：“别老想着给自己做产品了。 你仔细想想，你会花钱买自己做的东西吗？大概率不会。你就是那个最挑剔、意见最多、还一分钱不愿意掏的“白嫖怪”。 真正决定产品方向的，是那些愿意掏钱的人。 如果你的产品没人愿意付钱，八成是你找错了目标，跑偏了方向。”</p>
<p>是不是这个理？</p>
<p>另外，周刊转月刊了，最近非常懈怠，不知道怎么样能赚钱，不过知识分享不会停止。</p>
<p>Bye.</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[胡思乱想 - 生活 - 代码]]></title>
            <link>https://i5lin.top/blog/post/3053905586</link>
            <guid isPermaLink="false">3053905586</guid>
            <pubDate>Sat, 10 May 2025 08:29:06 GMT</pubDate>
            <description><![CDATA[一团乱麻]]></description>
            <content:encoded><![CDATA[<p><img src="https://de4965e.webp.li/blog-images/2025/05/1c4504983b52ca660a417bccbc044874.jpeg" alt="IMG_1243.jpeg"></p>
<p>2025年05月09日18:54:56 南京禄口机场 13 号登机口。</p>
<p>随便写写一些想法，想到哪里写哪里。</p>
<p>我的周刊上次更新是 53 天之前，我还以为是半个月前，这又是我半途而废的一件事之一？</p>
<p>我和我关注的前前前司的 CTO 大概前后相差不大的时间一起开始写周刊，但是人家的周刊数量已经是我的三倍。</p>
<p>🤣</p>
<p>笑哭，做一件事情能做好的关键之一：持之以恒。</p>
<p>我会继续做这件事情，周刊不会停。</p>
<p>我入职了这家公司算起来两个半月了，一开始是第三方的外包合同，再到某种特殊形式的新合同，这份合同最让我满意的是工作的性质。</p>
<p>谢天谢地，我终于可以体验一下远程开发的岗位了，尽管有一些曲折和折衷的东西，最后的结果还不错。</p>
<p>上一个月，花了很多时间处理搬家和租房的琐事，最终还是找到了一个还不错的房子租下来了。工作之余的时间写了一些项目，时间上就不太够去了解新东西和写周刊，当然也需要一点时间陪家人，也花了一点时间玩了 LOL。</p>
<p>这一个月写了一些代码，算是真正地经历了整个完整的全栈开发流程，这段经历堪称弥足珍贵。</p>
<p>如果不去做一些东西，不参与到核心的开发中来，不经历这些场景，我的自我成长是非常慢的。</p>
<p>无论你认可与否，有些人能够快速进步，效率是普通人的几倍。而我就是普通人，甚至是有些懒惰，经常间歇性停下的普通人，我做了一些东西，但却没有坚持下去。</p>
<p>到现在，我手头半途而废的东西不少。</p>
<p>五个。</p>
<p>五个我写出来又删掉了内容的项目，无论大小，这些未完成的东西绝对不应该公开，实在是太掉价了。</p>
<p>但这五个东西一直在我脑子里，我得计划一下，逐一实现这些东西。</p>
<p>这几天晚上都在处理国内 VPS 的 web 服务部署问题，国内的网络让我恶心坏了，为什么会有这么多恶心的网络问题，我真是一个坑一个坑地踩下去。</p>
<p>未来还是无可避免要处理国内 vps 的场景，或许应该好好谢谢脚本避免掉重复性的操作。</p>
<p>此外，部署也是一个很有趣的经历。</p>
<p>前端和后端项目写好了，通过 CI/CD 去构建镜像，推送到私有源，再通知服务器拉取镜像，重启服务。</p>
<p>私有源部署在国外的 vps 上，网络即可畅通无阻。</p>
<p>目前的项目为了方便，还是缺少很多东西，诸如版本管理、vps 资源监控、通知告警系统、日志归集系统。</p>
<p>不知不觉地渐渐需要做这些一年半之前在厦门的时候看到老胡做的事情，虽然没有别人做的那么丰富，但是也算是一条有趣的道路。</p>
<p>2025年05月10日15:47:18 柳州回融安的路上</p>
<p>早上睡过头了，错过了和朋友约好的早茶，简直太过离谱了，我简直是罪不可赦。</p>
<p>最近的作息出了点问题，睡觉得比以往更晚，坏习惯很难改掉。</p>
<p>当然，在这之外会有非常多的烦恼，让我手足无措，不知所措。</p>
<p>还是回到代码上来，前段时间有一个项目用到了腾讯云的云函数，为了查数据需要通过 token 和一些秘钥去通过 https 的接口去调取官方接口，于是按着文档去操作，在开发环境下成功调取到了数据，但是构建之后却遇到接口返回的 412 错误。</p>
<p>遇到这个问题的时候，我真是百思不得其解，简单的 api 调用在开发模式和构建之后调用会有差异，并且是在官网查不到的 412 错误码。</p>
<p>这个时候我只能想到希望能够查看官方接口的日志，但是很显然普通开发者如果使用云函数调用，绝对是不可能查到他们的日志的，完全是一个不可控的黑盒。</p>
<p>我终于明白为什么群里的朋友不想用这种云函数的方式操作数据。</p>
<p>最后在社区的人指点下手动给 ky 这个库手动添加了 content length 头字段，在构建之后才正常拿到数据。</p>
<p>忽然想到群里的 Jason 推崇 axios，axios 虽然老，但至少被广大的用户使用着，你能遇到的问题很多人都遇到过了。我喜欢 ky 这个新的请求库，但这次也遇到这种问题导致我难以排查出 content length 这个关键。</p>
<p>我还是很难理解，明明基于 fetch 的 ky 已经对 post 请求做了 content length 自动计算的逻辑，为什么 build 之后还会有这样的问题。</p>
<p>我没有正确的排查思路，当我将构建后的请求体复制到接口测试工具里去的时候，一切正常，我没有意识到 content length 被自动计算的关键。</p>
<p>或许应该对相同的输入做差异检查，例如通过 wireshark 检查请求的差异。</p>
<p>412 相应还是让我很无语，如果是 content length 的问题，为什么不是 413 呢？412 按照规范来说也是检查请求头的条件字段啊。。。</p>
<p>无论如何，这个问题也算是解决了。</p>
<p>我非常需要重新构建自己的脚手架，利用上完整的 monorepo 机制和 npm 公开 package，以及完整的测试、日志、项目管理的东西。</p>
<p>2025年05月10日16:03:53 融安</p>
<p>还有很多事情等着我处理，很多烦心的事情。</p>
<p>先这样吧，Bye</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[【译】Next.js 中间件漏洞深度剖析：深入探讨 CVE-2025-29927 授权绕过]]></title>
            <link>https://i5lin.top/blog/post/2941313976</link>
            <guid isPermaLink="false">2941313976</guid>
            <pubDate>Sun, 23 Mar 2025 16:47:00 GMT</pubDate>
            <description><![CDATA[本文探索了 Next.js 中间件中的关键漏洞 CVE-2025-29927，该漏洞允许攻击者绕过授权检查并获取未经授权的访问权限。]]></description>
            <content:encoded><![CDATA[<p>本文探索了 Next.js 中间件中的关键漏洞 CVE-2025-29927，该漏洞允许攻击者绕过授权检查并获取未经授权的访问权限。</p>
<h2>起因</h2>
<p>2025-03-21 日，在知名的 nvd 官网上发布了一则 Nextjs 框架高危漏洞的信息，这个框架在前端 SSR 场景下的使用率极高，因此笔者决定将本文翻译并且分析给大家。</p>
<blockquote>
<p><strong>美国国家漏洞数据库（National Vulnerability Database, NVD）</strong> 的官方网站。它由美国国家标准与技术研究院（NIST, National Institute of Standards and Technology）维护，是一个公开的漏洞信息资源库。</p>
</blockquote>
<p>原文链接如下：<a href="https://zeropath.com/blog/nextjs-middleware-cve-2025-29927-auth-bypass">Next.js Middleware Exploit: Deep Dive into CVE-2025-29927 Authorization Bypass - ZeroPath Blog</a></p>
<h2>介绍</h2>
<p>Next.js中间件，现代Web应用程序的基石，正面临严重的安全威胁。CVE-2025-29927暴露了一个重大的授权绕过漏洞，允许攻击者通过操纵x-middleware-subrequest头来绕过基于中间件的保护。此漏洞对依赖中间件进行身份验证和授权的应用构成了重大威胁，可能会授予未授权访问敏感资源。</p>
<p>受影响的系统和版本：</p>
<ul>
<li><strong>14.2.25之前</strong></li>
<li><strong>15.2.3之前</strong></li>
</ul>
<p>利用中间件进行授权检查的应用程序，如果没有做好头部验证，就特别容易受到攻击。</p>
<h2>技术细节</h2>
<p>核心问题来源于Next.js中间件中x-middleware-subrequest报头的过滤不足。中间件通常会拦截HTTP请求以实施安全策略。然而，攻击者可以通过在请求中注入x-middleware-subrequest报头来利用这一漏洞，从而绕过这些安全检查并获取未授权访问。</p>
<p>攻击面：</p>
<pre><code class="language-bash">GET /protected-route HTTP/1.1 
Host: vulnerable-app.com 
x-middleware-subrequest: true
</code></pre>
<p>中间件逻辑如果无法验证或阻止该标头，会无意中授予访问权限。如果在安全版本上打补丁不可行，则建议阻止包含x-middleware-subrequest标头的外部用户请求到达您的Next.js应用。此漏洞在14.2.25和15.2.3中得到修复。</p>
<h2>其他</h2>
<p>至此，后续为非翻译内容。</p>
<p>根据 github issue 的一些信息来看，这个漏洞的起因是 Vercel 的开发人员为了防止中间件内部发起的请求可能导致再次进入中间件导致的循环问题而想到的解决方案：在请求头中添加标识 <code>x-middleware-subrequest: true</code>。</p>
<p>🤣</p>
<p>如果你使用的某些库将授权逻辑封装在中间件中，那么就可能会收到这个漏洞的影响，攻击者可以绕过通过中间件保护的接口和服务。</p>
<p>如果升级困难的话，其实也可以在 Nginx 或者 WAF 这种网关设备直接将带有这个请求头标识的外部请求拦截掉即可。</p>
<h2>最后</h2>
<p>上周比较忙，周刊稍等片刻~先允许我🕊一会</p>
<p>Bye</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.74]]></title>
            <link>https://i5lin.top/blog/post/2905523874</link>
            <guid isPermaLink="false">2905523874</guid>
            <pubDate>Sun, 09 Mar 2025 16:20:46 GMT</pubDate>
            <description><![CDATA[周刊速递，来来来走过路过别错过~]]></description>
            <content:encoded><![CDATA[<p>Hello，大家好！本周给大家带来了一些有趣的开源项目和工具，涵盖 AI、函数式编程、前端组件库、文件分享、数据分析等多个方向。</p>
<h2>OpenManus</h2>
<p><a href="https://github.com/mannaandpoem/OpenManus" title="mannaandpoem/OpenManus: No fortress, purely open ground. OpenManus is Coming.">mannaandpoem/OpenManus: No fortress, purely open ground. OpenManus is Coming.</a></p>
<p>OpenManus 是一个开源项目，由 MetaGPT 团队开发，是 Manus 的无样式复刻版。它提供基础 AI 组件，支持模块化设计，可通过简单配置运行本地智能体，帮助用户自动化任务。无需邀请码，适合开发者快速集成和自定义，附带多种示例和用例。</p>
<p>关键是这玩意不需要你去哪搞什么邀请码 🤣</p>
<h2>Ramda</h2>
<p><a href="https://ramda.cn/" title="Ramda Documentation">Ramda Documentation</a> 是一款实用的 JavaScript 函数式编程库。某种意义上来说，你可以用它来替换掉 lodash，并且如果你是函数式编程的 Fans 的话，你可能会感到兴奋。
Ramda 主要特性如下：</p>
<ul>
<li>Ramda 强调更加纯粹的函数式风格。数据不变性和函数无副作用是其核心设计理念。这可以帮助你使用简洁、优雅的代码来完成工作。</li>
<li>Ramda 函数本身都是自动柯里化的。这可以让你在只提供部分参数的情况下，轻松地在已有函数的基础上创建新函数。</li>
<li>Ramda 函数参数的排列顺序更便于柯里化。要操作的数据通常在最后面。</li>
</ul>
<h2>Reka</h2>
<p><a href="https://reka-ui.com/" title="Reka UI - Unstyled, fully accessible UI library | Reka UI">Reka UI - Unstyled, fully accessible UI library | Reka UI</a> ，真是一个开源的库，无样式的基础组件，并附带各种示例和用例。</p>
<p>又一个 Vue 组件库，这样的库真是层出不穷，难免让人有些审美疲劳。</p>
<h2>FileCodeBox</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/c9d8fd466675c505de3ce359ccbbc55d.png" alt="image.png"></p>
<p><a href="https://github.com/vastsa/FileCodeBox" title="vastsa/FileCodeBox: 文件快递柜-匿名口令分享文本，文件，像拿快递一样取文件（FileCodeBox - File Express Cabinet - Anonymous Passcode Sharing Text, Files, Like Taking Express Delivery for Files）">vastsa/FileCodeBox: 文件快递柜-匿名口令分享文本，文件，像拿快递一样取文件（FileCodeBox - File Express Cabinet - Anonymous Passcode Sharing Text, Files, Like Taking Express Delivery for Files）</a></p>
<p>使用以下技术栈开发：</p>
<ul>
<li>Python 3.8+</li>
<li>Node.js 14+</li>
<li>Vue 3</li>
<li>FastAPI
前后端都可以参考的文件暂存、用户取件存件的系统服务，学习完了这个系统你就可以轻松构建自己的相关服务。</li>
</ul>
<h2>umami</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/c652859d4c0b153fd916a9bab8317f1e.png" alt="image.png"></p>
<p><a href="https://umami.is/" title="Umami">Umami</a>是一个开源的网站数据分析应用，主打简单、快速、注重隐私的谷歌分析替代品。</p>
<h2>Base UI + 9ui</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/eebb6a9e77dd2c11a9a121f7b7987e6c.png" alt="image.png"></p>
<p><a href="https://base-ui.com/" title="Base UI">Base UI</a>：如果你厌倦了千篇一律的 Radix UI 及其衍生组件，可以看看这个 Radix UI 竞品，以及 Shadcn UI 的对标竞品：<a href="https://github.com/borabaloglu/9ui?tab=readme-ov-file" title="borabaloglu/9ui: Beautiful, customizable components built with Base UI and Tailwind CSS.">borabaloglu/9ui: Beautiful, customizable components built with Base UI and Tailwind CSS.</a>，这是一个你可以复制粘贴到项目中的组件集合。</p>
<p>基于 Base UI 和 Tailwind CSS 构建。易于定制。自由开源。</p>
<h2>TG 图床</h2>
<p><a href="https://github.com/cf-pages/Telegraph-Image" title="cf-pages/Telegraph-Image: Image Hosting solution, Flickr/imgur alternative, make it easy for users to share their images. Using Cloudflare Pages and Telegraph.">cf-pages/Telegraph-Image: Image Hosting solution, Flickr/imgur alternative, make it easy for users to share their images. Using Cloudflare Pages and Telegraph.</a></p>
<p>免费图片托管解决方案，Flickr/imgur 替代品。使用 Cloudflare Pages 和 Telegraph。</p>
<h2>Autosuggest highlight</h2>
<p><a href="https://github.com/moroshko/autosuggest-highlight" title="moroshko/autosuggest-highlight: Utilities for highlighting text in autosuggest and autocomplete components">moroshko/autosuggest-highlight: Utilities for highlighting text in autosuggest and autocomplete components</a></p>
<p>文本高亮工具，用于在自动提示和自动补全组件中突出显示文本。如果你需要处理一些基于文本的关键词搜索，并且针对搜索结果高亮，那么这个库可以节省你很多时间。</p>
<h2>前端本地 Mock 数据</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/2f9ade272f6ab6b1f2e13f44db39a485.png" alt="image.png"></p>
<p><a href="https://mswjs.io/" title="Mock Service Worker - API mocking library for browser and Node.js">Mock Service Worker - API mocking library for browser and Node.js</a> 可以让你的前端项目通过 web worker 模拟出接口的数据，如果你需要部署一个模拟具有后端服务的前端应用，那么可以考虑看看这个工具。</p>
<h2>Vite-plugin-svg-icons</h2>
<p><a href="https://github.com/vbenjs/vite-plugin-svg-icons/tree/main" title="vbenjs/vite-plugin-svg-icons: Vite Plugin for fast creating SVG sprites.">vbenjs/vite-plugin-svg-icons: Vite Plugin for fast creating SVG sprites.</a> 是一个 vite 插件，用于将指定目录下的 svg 图片打包成 svg 精灵图，这样你就可以通过字符串构建一个 svg 组件。有些集成 svg 精灵图的场景很有用。</p>
<h2>图</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/9e7a10661e6c35f7f449a7aa6d086b1c.png" alt="image.png"></p>
<h2>最后</h2>
<p><del>最近开始准备一直在找远程工作，如果大家有内推的话，欢迎联系我！</del></p>
<p>搬砖了！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.73]]></title>
            <link>https://i5lin.top/blog/post/2889640059</link>
            <guid isPermaLink="false">2889640059</guid>
            <pubDate>Sun, 02 Mar 2025 16:13:03 GMT</pubDate>
            <description><![CDATA[🎉 公众号关注人数超过 200 大关，绝大多数都是最近半年新增的关注，这让我非常开心。]]></description>
            <content:encoded><![CDATA[<p>🎉 公众号关注人数超过 200 大关，绝大多数都是最近半年新增的关注，这让我非常开心。本质上，我是个懒人，断断续续地写一些文字有时候很难坚持，但最近收到了一些小小的正向反馈，希望接下来自己能够多读、多看、多学、多写，以及多谢大家。</p>
<p>OK，本周要分享的内容如下：</p>
<h2>DeepLearning</h2>
<p><a href="https://learn.deeplearning.ai/courses/build-apps-with-windsurfs-ai-coding-agents">Deep Learning - Build apps with windsurf&#39;s AI coding agents</a></p>
<p>Windsurf 发布了一个如何使用 AI 开发一个完整的应用的视频教程，笔者在这里分享给每一个开发者朋友，时代真的变了，我们得了解如何更好地利用 AI 来开发，而不是对 AI 的发展视若无睹。</p>
<p>否则，可能有一天我们会被彻底淘汰。
PS：尤其是前端程序员 🤣</p>
<h2>Eva Design System</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/70416eb08860798ca68a497f8f2e750c.png" alt="image.png"></p>
<p><a href="https://colors.eva.design/">Eva Design System: Deep learning color generator</a>是一个颜色设计方案推荐系统，作者对颜色搭配和主题设计颇有研究，我在某个项目的源代码中看到这个地址，因此发现了它。
如果你想要为自己的应用设计一套良好的颜色组合，可以试试这个应用。</p>
<h2>Light panda</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/f192b786358581ff179ef139dc856258.png" alt="image.png"></p>
<p><a href="https://lightpanda.io/?utm_source=indiehub.best&utm_medium=referral&utm_campaign=navigation">Lightpanda | The headless browser</a> 是一个从头开始构建的、基于AI的无头浏览器，旨在以快速、可扩展的方式进行网页自动化，同时占用最小内存。</p>
<blockquote>
<p>世上没有免费午餐，即要快还要占用低，大概率牺牲了一些安全性和功能完整性。当然，新的性能和效率优化具有一定的效果，笔者并没有深入对比二者的差别。</p>
</blockquote>
<p>如上所示，在一个相对基准上对比谷歌浏览器，Lightpanda 具有更快的速度和更低的内存占用。与传统浏览器不同，PandaLight 不是 fork 或 patch，而是一个完全新型的浏览器，专为性能和效率而构建。</p>
<p>我想如果你有一个不需要究极严谨和安全的无头浏览器自动化需求，那么 lightpanda 不错，你可以部署在云服务器上。</p>
<h2>Nothing New Tab</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/68289c365a450edc4116066b06bb6584.png" alt="image.png">
<a href="https://github.com/ImRayy/nothing-ui-newtab?tab=readme-ov-file">ImRayy/nothing-ui-newtab</a>是一个 chrome 新标签页插件，页面设计得非常有特色，看起来很棒。</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/1a787feb1dd194726de29a7accff30fa.png" alt="image.png"></p>
<h2>你的第一门用 TypeScript 写 Node.js 的入门课</h2>
<p><img src="https://npmstudy.github.io/Your-First-Node.js-with-TypeScript-Course/img/cover.png" alt=""></p>
<p><a href="https://npmstudy.github.io/Your-First-Node.js-with-TypeScript-Course/">前言</a>：AI时代放大了全栈的好处，很多以前我们觉得可以吃饭的一些技能都变成了必备技能，IT技术平民化，原因有2个。</p>
<p>1、AIGC时代，风口，快速实现更重要，资本不等人。</p>
<p>2、IT技术平民化，全栈化，必须要简单高效。</p>
<p>这样筛选下来，只有2种可能的语言，那就是Node.js和Python。Python是大模型必备，搞定服务端也是很轻松的，而要搞定前端和服务端，成本最低的就是用同一种语言，无疑Node.js是最好的选择。毕竟现在SDK标准三个语言必须实现，那就是Java、Node.js和Python。</p>
<p>除了AI时代放大了Node全栈外，Node.js自身的发展也到了一个必须要总结的时间点了。目前市面上的书、文章、课程主要是Node.js 8以前的（不知道朴灵的深入浅出2能否有突破），现在已经是Node.js v20了。Node.js v20里的很多新特性，比如下面是我觉得比较重要的一些。</p>
<p>PS: 作者是一个很有经验的开发者，写过中文 Node 不少相关书籍，多年前见过一面，很有喜气 🤣，这篇分享可以看成一本书，我打算花点时间跟着看完学习一下。</p>
<h2>AI art icon</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/6afd48a189675e5285fde0b6b3f42b8f.png" alt="image.png"></p>
<p><a href="https://www.figma.com/community/file/1401116914280866478">AI art Icon | Figma</a> 是 Figma 上的一系列关于 AI 相关主题的图标，无论是设计师还是开发者，都可以用得上。</p>
<h2>UniBest</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/bfbbccb1a78ec0aec1c959c5cc0a4ff7.png" alt="image.png"></p>
<p><a href="https://github.com/feige996/unibest">feige996/unibest: unibest - 最好用的 uniapp 开发框架。unibest 是由 uniapp + Vue3 + Ts + Vite5 + UnoCss + WotUI 驱动的跨端快速启动模板，使用 VS Code 开发，具有代码提示、自动格式化、统一配置、代码片段等功能，同时内置了大量平时开发常用的基本组件，开箱即用，让你编写 uniapp 拥有 best 体验。</a></p>
<p>短平快的项目如果必须要用 uniapp 开发，那么 unibest 提供的体验确实非常适合，感谢开源贡献者们。</p>
<h2>Z-paging</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/ddfe799d69350db9eba7650572d5bd86.png" alt="image.png"></p>
<p><a href="https://z-paging.zxlee.cn/">z-paging</a> 是一款高性能，全平台兼容(ios、安卓、鸿蒙next、h5以及各家小程序)的 uniapp 分页组件。支持vue、nvue、vue2、vue3，使用wxs+renderjs实现。</p>
<p>支持虚拟列表，支持自定义下拉刷新、上拉加载更多，支持自动管理空数据图、点击返回顶部，支持聊天分页、本地分页，支持国际化等数百项配置，流畅渲染百万级列表数据。</p>
<p>如果需要短平快地解决 uniapp 应用中的下拉刷新、上拉加载更多、虚拟列表渲染大量数据的场景，可以了解了解这个。</p>
<p>如上所示，unibest 已经集成了这个组件 🎉</p>
<h2>fast-npm-meta</h2>
<p>fast-npm-meta 的仓库在：<a href="https://github.com/antfu/avatars.antfu.dev">https://github.com/antfu/avatars.antfu.dev</a> ，这是一个封装好的 npm lib 元信息查询库。</p>
<p>起源是 npm 仓库的 API 相对较老且效率不高。例如，请求 <a href="https://registry.npmjs.org/vite">https://registry.npmjs.org/vite</a> 会使你下载 4.38 MB 的数据，这代表了 36.32 MB 的未压缩的 JSON 数据。仅为了获取单个包的最新版本，要下载这么多数据，这确实是个问题。</p>
<p>这个项目旨在提供一个轻量级API服务器作为代理，它缓存来自npm注册表的元数据，并提供了一种更高效的方式来解决软件包版本的问题。</p>
<p>某些场景下，如果开发者需要检索 npm 的包的元信息，可以使用这个封装好的库。</p>
<p>PS：不过这个需求真的很小众。</p>
<h2>Better Auth</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/9b530dc63d3088b9f14359c5283118b9.png" alt="image.png"></p>
<p><a href="https://www.better-auth.com/">Better Auth</a> 是一个 TypeScript 最全面的身份验证框架，文档详细，开发体验非常好。</p>
<p>支持流行的框架，包括 React、Vue、Svelte、Astro、Solid、Next.js、Nuxt、Tanstack Start、Hono 等。</p>
<p>集成支持电子邮件和密码认证，具备会话和账户管理功能。允许用户使用他们的账户登录，包括GitHub、Google、Discord、Twitter等等。使用几行代码就能通过双重认证保护用户帐户。</p>
<h2>Avatar Generator</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/21cce98fa7f24e30e3efe7cc4627e7d1.png" alt="image.png"></p>
<p><a href="https://github.com/mortoys/avatar-generator">mortoys/avatar-generator: 基于贝叶斯网络的捏脸生成器</a>，这...</p>
<p>有这需求？但这确实有一定的价值。</p>
<h2>图</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/31b3e5e6bacb193ef214202cb7f52e76.png" alt="image.png"></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/03/830a50a0c2928f655f41595719b15880.png" alt="image.png">
<img src="https://de4965e.webp.li/blog-images/2025/03/0b0073026212b0f3d3f523a7d0705c5d.png" alt="image.png"></p>
<h2>最后</h2>
<p>最近<del>开始准备</del>一直在找远程工作，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.72]]></title>
            <link>https://i5lin.top/blog/post/2873204051</link>
            <guid isPermaLink="false">2873204051</guid>
            <pubDate>Sun, 23 Feb 2025 13:40:57 GMT</pubDate>
            <description><![CDATA[这周资讯来啦：Creem 让国内开发者收款更简单；Cherry Studio 新增联网搜索超实用；Cline 是 VSCode 的 AI  coding 神器；Nuqs 搞定 React 状态同步...]]></description>
            <content:encoded><![CDATA[<p>Hello，大家好。这周我给大家带来了一些新鲜资讯，简单聊聊都有什么：</p>
<ul>
<li><strong>Creem</strong>：国内开发者现在能用身份证和支付宝收款啦，海外支付终于不那么麻烦了。</li>
<li><strong>Cherry Studio</strong>：开源 AI 聊天工具，能写代码还能搜网，最新版 v1.0.0 beta 超实用。</li>
<li><strong>Cline</strong>：VSCode 的 AI 助手，能改代码、跑命令，代替 Cursor 超省钱。</li>
<li><strong>Nuqs</strong>：React 的 URL 状态同步神器，分享链接还能保持状态。</li>
<li><strong>Una UI</strong>：Vue3 的原子化 UI 框架，用 UnoCSS 驱动，扩展性很强。</li>
<li><strong>Mastra.ai</strong>：TypeScript 的 AI 框架，功能超全，但团队背景有点冷门。</li>
<li><strong>MoePush</strong>：NextJS + Cloudflare 的消息推送服务，简单又好学。</li>
<li><strong>LimTuber</strong>：Mac 上的极简 YouTube 播放器，专注看视频无干扰。</li>
<li><strong>FREEPIK</strong>：设计资源站，插画图标啥都有，部分免费。</li>
<li><strong>Grok v3</strong>：马斯克的新模型，5 刀月租送 150 刀额度，还能免费试用。</li>
<li><strong>Animate.css</strong>：CSS 动画库，预置动画超方便，开发快人一步。</li>
</ul>
<h2>Creem</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/f366a08cb5be70702b91285208ec87a8.png" alt="image.png"></p>
<p>最新消息，<a href="https://www.creem.io/">Creem</a> 支持国内的开发者通过身份证和支付宝认证即可收款了。此前海外收款一直非常麻烦，各个平台甚至需要护照、港卡、海外地址甚至是海外公司，现在让我们先观望观望这家公司实际的服务到底如何，心急的同行可以主动去了解了解。</p>
<h2>Cherry Studio</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/7decaf7c52492930976d15f39321ed5a.png" alt="image.png"></p>
<p>Cherry Studio 是个开源的 AI 聊天客户端，能用多个大模型，像编程、写作啥的都行。</p>
<p>最近 v1.0.0 beta 版本给所有模型加了联网搜索功能，堪称核弹级更新。</p>
<h2>Cline</h2>
<p>认识 Cline，一个可以使用你的 <strong>CLI</strong> 和 <strong>编辑器</strong> 的 AI 助手：</p>
<p><img src="https://media.githubusercontent.com/media/cline/cline/main/assets/docs/demo.gif" alt=""></p>
<p>Cline 是一个开源的 VSCode 扩展，利用先进的 AI 模型（比如 Anthropic 的 Claude 3.5 Sonnet 或其他支持的模型）提供强大的编码支持。</p>
<p>它通过与你的代码编辑器深度集成，能够直接操作文件、执行终端命令、分析项目结构，甚至通过浏览器进行交互式调试。</p>
<p>它的特别之处在于“以人为中心的控制”，每次 AI 执行关键操作（比如修改文件或运行命令）时，都会请求你的批准，确保你始终掌控整个过程。</p>
<p>某种意义上说，如果你没法用 Cursor （或者嫌他太贵），那么可以使用这个 vscode 插件配合上 DeepSeek API 来实现类似的效果：AI 辅助编程。</p>
<h3>怎么开始用？</h3>
<ol>
<li>在 VSCode 的扩展市场搜索“Cline”并安装。</li>
<li>配置 API 密钥（支持 OpenAI、Anthropic、Google Gemini 等多种模型，推荐用 OpenRouter 获取灵活选择）。</li>
<li>打开 Cline 侧边栏，输入你的任务（可以用中文，比如“帮我写个 Python 脚本”），然后根据提示批准或调整 AI 的操作。</li>
</ol>
<h2>Nuqs</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/51a415833b18b5593e9eaaeeab858407.png" alt="image.png"></p>
<p><a href="https://nuqs.47ng.com/">nuqs | Type-safe search params state management for React</a> ：Nuqs 是一个类型安全的 URL 管理同步状态的 React 库，使用这个库可以让你轻松在 URL 中定义和同步一些 State。</p>
<p>说人话：在 React Admin 应用中同步 useState 的值和地址栏 query 字段，提供持久化和初始化的能力，用户可以通过分享 URL 访问的时候得到良好的体验。</p>
<p>此外，这个库还对通过 URL 传输数据做了序列化支持，抹平了前端通过 Get 请求中传递的数据类型和后端对接时的差异。</p>
<p><strong>这绝对是一个值得每一个 React 开发者学习和使用的库。</strong></p>
<h2>Una UI</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/4ec74ec81dd90f5091139ce7eb8e5949.png" alt="image.png"></p>
<p><a href="https://unaui.com/">Una UI</a> 是一个由 UnoCSS 引擎驱动的原子化的 Vue3 UI 框架，内置系统主题和 TypeScript 支持，具有良好的扩展性，支持 Nuxt SSR。
如果你是 Vue3 + UnoCSS 的用户，那么这个框架一定会让你感到非常满意。</p>
<h2>Mastra.ai</h2>
<p><a href="https://mastra.ai/">The Typescript AI framework - Mastra</a> 是前全栈框架 Gatsby 的团队打造的一个 TypeScript Agent 框架，其融合了 Agents、Tools、Workflow、RAG、评测和各种集成设计，功能非常强大。</p>
<blockquote>
<p>话外：不过Gatsby 都快死了，几年前流行过一段时间，看来还是不要浪费时间在没有足够生命力的热门项目上。</p>
</blockquote>
<h2>MoePush</h2>
<p><a href="https://github.com/beilunyang/moepush">beilunyang/moepush: 一个基于 NextJS + Cloudflare 技术栈构建的可爱消息推送服务, 支持多种消息推送渠道✨</a></p>
<p>功能非常简单，但对于学习 Nextjs 和想要接触 Cloudflare 的一系列服务的开发人员来说，具有一定的学习价值。</p>
<h2>LimTuber</h2>
<p><img src="https://limstack.com/limtuber-screenshot.png" alt="">
<a href="https://limstack.com/limtuber">LimTuber</a> 是一个极简的原生 MacOS APP，其核心功能就是通过添加 YouTube 播放列表地址，将其视频列表引入这个 APP。</p>
<p>作者说：如果你像我一样，只是想安安静静地观看YouTube播放列表，而不受任何干扰，那么这款应用就是为你设计的。</p>
<h2>FREEPIK</h2>
<p><a href="https://www.freepik.com/">Freepik | Create great designs, faster</a>提供快速设计 UI 的一切资源，你可以在这里找到非常多的插画、图标、视频和原型等等（部分是免费的）。</p>
<h2>Grok v3</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/4e0f97d270a18ae43102e50b22f115df.png" alt="image.png"></p>
<p>马斯克最近发布了 Grok3 模型，并且只要你同意数据共享（牺牲点隐私）就可以月租 5 刀的同时每个月送你 150 刀的 API 额度，并且 Grok3 支持 DeepSearch。</p>
<p>此外，Grok3 宣布免费用户可以使用，并且包含推理模型。试了一下web和app都可能直接用了，但是免费用户的次数是有限制的。</p>
<p>稍微问问它一个学英语的问题：
<img src="https://de4965e.webp.li/blog-images/2025/02/689262ef66c94a17a950254762d34c67.png" alt="image.png"></p>
<h2>Animate.css</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/b303f3859d96c0c6efea4ce5e93bfcdd.png" alt="image.png"></p>
<p><a href="https://animate.style/">Animate.css | A cross-browser library of CSS animations.</a>：跨浏览器的 CSS 动画库，其提供了非常流行的各种 CSS 动画的预置类，开发者可以快速接入节省时间。</p>
<h2>最后</h2>
<p>最近<del>开始准备</del>一直在找远程工作，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.71]]></title>
            <link>https://i5lin.top/blog/post/2857701311</link>
            <guid isPermaLink="false">2857701311</guid>
            <pubDate>Mon, 17 Feb 2025 12:23:32 GMT</pubDate>
            <description><![CDATA[每天都是重复，大脑自然就会压缩对时间的感受。]]></description>
            <content:encoded><![CDATA[<p>Hello，大家好。本周给大家带来的一些讯息如下：</p>
<h2>Trae</h2>
<p><a href="https://www.trae.ai/">Trae - Ship Faster with Trae</a>是字节跳动开发的一款 AI 编辑器，与Cursor、Windsurf同属AI辅助编程赛道，但专注中文开发者生态，目前笔者体验一周下来，基本效果还是很满意的，响应速度很快（难道是我神器好？），不过有些代码生成的时候会时不时抽风，把一些有用的代码删除导致整个文件不正常。</p>
<p>最新消息，字节发布了 Windows 版本的 Trae 编辑器 🎉</p>
<h2>Juchats</h2>
<p>!<a href="https://de4965e.webp.li/blog-images/2025/02/22bf757d6fdcac354a110740ae707667.png">https://de4965e.webp.li/blog-images/2025/02/22bf757d6fdcac354a110740ae707667.png</a></p>
<p><em>Juchats</em>（巨有料），是由 X 用户 <code>@Cydiar</code> 开发的一款聚合了多个大模型的AI对话应用。经过2 年的迭代和更新，这个应用的交互体验已经非常好了。</p>
<blockquote>
<p>但你要是问我，它到底哪点能让你不选择 Cursor？或许是它免费吧。</p>
</blockquote>
<p>目前，众多模型都提供每日一定的额度体验，支持联网功能的 DeepSeek R1 和 V3 更是无限制使用。</p>
<p>在这里推荐给大家~</p>
<h2>Sablejs</h2>
<p><a href="https://github.com/sablejs/sablejs">sablejs/sablejs: 🏖️ The safer and faster ECMA5.1 interpreter written by JavaScript</a></p>
<p>SableJS 是一个使用 JavaScript 编写的更快、更安全的 ECMAScript 5.1 解释器。它可以用于多种场景，例如：</p>
<ul>
<li><strong>沙箱环境：</strong> 类似于 Figma 插件沙箱，但更易于使用。</li>
<li><strong>小游戏/小程序动态执行：</strong> 在小游戏或小程序中动态执行 JavaScript 代码。</li>
<li><strong>将 JavaScript 编译为字节码：</strong> 通过提前编译（AOT）将 JavaScript 代码编译为字节码，提高执行效率。</li>
</ul>
<p>浏览器端逆向代码逻辑的难度不高，在大多数前端安全性要求较高的场景下，开发者需要一些特殊的手段隐藏和保护核心的逻辑。</p>
<p>较为出名的方式是通过 <code>WebAssembly</code>的方式，在独立于 JavaScript 环境下单独执行二进制的内容，以此获取较高的安全性。
但是，这种方案一旦需要访问 <code>DOM</code>等等 JavaScript 引擎的内容时，需要使用 <code>eval</code>函数和 JavaScript 引擎通信。这是不够安全的，某些开发者完全有能力在 WebAssembly 执行前对 eval 进行复写拦截，从而获取到里面执行的 JavaScript 代码字符串。</p>
<p>为了获得一个兼具 <code>WebAssembly</code>和 <code>JavaScript</code>引擎的优点的环境，这个工具的开发团队打算使用 <code>JavaScript</code>去开发一个 <code>JavaScript</code>解释器，并且他们获得了成功🎉。</p>
<p>如果你需要保护你的某些安全性要求高的代码逻辑，那么可以考虑这个工具。</p>
<h2>isd</h2>
<p><a href="https://isd-project.github.io/isd/">isd</a> 是一个优化命令行管理服务器 systemd 服务的命令行程序，熟悉运维的开发者或许会对输入 systemd 服务的命令感到厌烦，一来这类服务较为冗长，容易输入错误，二来还需要记住众多的服务名称和功能。</p>
<p>isd 这个命令行程序提供了较好的基于命令行终端的 UI 交互，可以提高运维人员的操作体验。</p>
<p>!<a href="https://de4965e.webp.li/blog-images/2025/02/90b5aef5a756967336372eec284fe4ef.png">https://de4965e.webp.li/blog-images/2025/02/90b5aef5a756967336372eec284fe4ef.png</a></p>
<p>这里还有一篇使用教程：<a href="https://linuxiac.com/isd-is-a-new-tool-offering-a-user-friendly-approach-to-systemd-management/">ISD Is a New Tool Offering a User-Friendly Approach to Systemd Management</a></p>
<h2><a href="http://changedetection.io/">changedetection.io</a></h2>
<blockquote>
<p>dgtlmoon/changedetection.io: The best and simplest free open source web page change detection, website watcher, restock monitor and notification service. Restock Monitor, change detection. Designed for simplicity - Simply monitor which websites had a text change for free. Free Open source web page change detection, Website defacement monitoring, Price change notification</p>
</blockquote>
<p>有一个做云服务器相关的大 V 经常在社交媒体上更新某个服务商提供了最新的产品，亦或是因为货源短缺暂时停止出售的某些服务又开始出售的消息。
为什么有些人能这么快地知道某个商品的降价？难道他时时刻刻盯着这些页面吗？</p>
<p>显然不是的，<a href="http://changedetection.io/">changedetection.io</a> 这样的开源服务可以轻松通过 docker 部署到本地或服务器上，用户可以指定特定页面的某个元素进行监控，并且在目标变更之后立即通知用户。</p>
<h2>Swagger TypeScript API</h2>
<p><a href="https://github.com/acacode/swagger-typescript-api/tree/main">acacode/swagger-typescript-api: Generate the API Client for Fetch or Axios from an OpenAPI Specification</a></p>
<p>后端服务接口支持 OpenAPI 规范（Swagger API），并且提供每个接口的描述文件，前端可以通过这样的工具生成接口请求相关的函数。</p>
<p>这样的工具有两个好处：</p>
<ol>
<li><strong>自动化降本</strong>：将接口文档直接转换为 TypeScript 请求层代码，减少手动编写重复 CRUD 代码的时间成本。</li>
<li><strong>类型安全保障</strong>：根据 OpenAPI 的 <code>schema</code> 生成精准的请求/响应类型定义（参考 <a href="https://juejin.cn/post/7293122700659048499">Swagger 生成文档实践</a>），提升代码健壮性。</li>
</ol>
<p>通过自动化工具生成的代码具有良好的类型支持，也可以通过定时同步功能防止前后端协作沟通的延时问题，避免造成接口错误。</p>
<p>但是，对于一些灵活性的接口，这种方式也需要开发者辅助去做一些微调，即便如此，这种方案也非常适合中小型标准化 CRUD 项目。</p>
<h2>Slash Admin</h2>
<p><a href="https://camo.githubusercontent.com/6acd165e214ba9542cd39eeab38b3a109df7f443b45fe1bab23ee455e0ef9060/68747470733a2f2f643367656f7267652e6769746875622e696f2f6769746875622d7374617469632f736c6173682d61646d696e2f6c6f67696e2e6a706567">https://camo.githubusercontent.com/6acd165e214ba9542cd39eeab38b3a109df7f443b45fe1bab23ee455e0ef9060/68747470733a2f2f643367656f7267652e6769746875622e696f2f6769746875622d7374617469632f736c6173682d61646d696e2f6c6f67696e2e6a706567</a></p>
<p><a href="https://camo.githubusercontent.com/06984c6ab7b12e4674c22bae24d34f401e0dbcb60eab050df2c4c54896537408/68747470733a2f2f643367656f7267652e6769746875622e696f2f6769746875622d7374617469632f736c6173682d61646d696e2f616e616c797369732e706e67">https://camo.githubusercontent.com/06984c6ab7b12e4674c22bae24d34f401e0dbcb60eab050df2c4c54896537408/68747470733a2f2f643367656f7267652e6769746875622e696f2f6769746875622d7374617469632f736c6173682d61646d696e2f616e616c797369732e706e67</a></p>
<p><a href="https://github.com/d3george/slash-admin"><strong>slash-admin</strong></a> 是一个基于 React 技术栈的后台管理模版项目，其特点如下：</p>
<ul>
<li>使用 React 18 hooks 进行构建。</li>
<li>基于 Vite 进行快速开发和热模块替换。</li>
<li>集成 Ant Design，提供丰富的 UI 组件和设计模式。</li>
<li>使用 TypeScript 编写，提供类型安全性和更好的开发体验。</li>
<li>响应式设计，适应各种屏幕尺寸和设备。</li>
<li>灵活的路由配置，支持多级嵌套路由。</li>
<li>集成权限管理，根据用户角色控制页面访问权限。</li>
<li>集成国际化支持，轻松切换多语言。</li>
<li>集成常见的后台管理功能，如用户管理、角色管理、权限管理等。</li>
<li>可定制的主题和样式，以满足您的品牌需求。</li>
<li>基于 MSW 和 Faker.js 的Mock方案</li>
<li>使用 Zustand 进行状态管理</li>
<li>使用 React-Query 进行数据获取</li>
</ul>
<p>此外，最重要的还是这个界面很好看 🤣，其次核心技术栈都非常具有实用价值和学习价值。</p>
<h2>Jimp</h2>
<p><a href="https://jimp-dev.github.io/jimp/">Jimp | Jimp</a> 是一个轻量级、无依赖的 JavaScript 图片操作库，支持图像裁剪、调整尺寸、模糊处理、滤镜添加（如灰度化）、格式转换（JPEG/PNG/WEBP等）及文字叠加。</p>
<p>其一大优点是纯 JavaScript 实现，无依赖 C++ 模块（如 <code>sharp</code>），避免跨平台编译问题（参考<a href="https://javascript.net.cn/articles/803">Node.js 模块对比</a>）。适合中小型图像处理任务（如生成缩略图），与 <code>node-image</code>、<code>sharp</code> 相比更易上手（详见<a href="https://alienzhou.github.io/fe-performance-journey/5-subresources/image.html">性能优化指南</a>）。</p>
<p>劣势便是在处理大尺寸图片（如 4K+）时性能低于 <code>sharp</code>（C++底层优化）。</p>
<p>如果你需要对小尺寸图片进行细微的调整，那么这个库很方便。</p>
<h2>Widget Examples</h2>
<p><a href="https://github.com/pawello2222/WidgetExamples">pawello2222/WidgetExamples: A demo project showing different types of Widgets created with SwiftUI and WidgetKit.</a>
这是一个 IOS 开发 Swift 合集，提供了常见的 Widget 学习模版。</p>
<h2>Shiki-Stream</h2>
<p><a href="https://github.com/antfu/shiki-stream">antfu/shiki-stream: Streaming highlighting with Shiki. Useful for highlighting text streams like LLM outputs.</a></p>
<p>这是前端托尼老师最新分享的基于 Shiki 的流式代码高亮工具，这对于高亮显示如 LLM 输出之类的流文本非常有用。</p>
<p>举个例子，你可以为 AI 对话生成代码的时候，生成流式的高亮内容，增强用户体验。</p>
<h2>言论</h2>
<ul>
<li>每天都是重复，大脑自然就会压缩对时间的感受。</li>
<li>某程序员：“行业铁律“过早优化是原罪”，技术是为产品服务的，技术层面花里胡哨产品却不好好设计，最终就是被市场淘汰。” vs 某程序员：“有道理，但是被市场淘汰的是企业、公司，而不是折腾技术的个人，不折腾技术的个人反而会被行业淘汰。”</li>
<li>🤣</li>
</ul>
<p>!<a href="https://de4965e.webp.li/blog-images/2025/02/c19533d032e04a1886297c615cf3f2b1.png">https://de4965e.webp.li/blog-images/2025/02/c19533d032e04a1886297c615cf3f2b1.png</a></p>
<h2>最后</h2>
<p>最近<del>开始准备</del>一直在找远程工作，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[为什么我的 Tailwind css 类没生效]]></title>
            <link>https://i5lin.top/blog/post/2850687306</link>
            <guid isPermaLink="false">2850687306</guid>
            <pubDate>Thu, 13 Feb 2025 10:55:52 GMT</pubDate>
            <description><![CDATA[safelist 是 Tailwind CSS v3 中一个非常有用的功能，可以帮助你解决一些特殊情况下类名无法被检测到的问题。合理使用 safelist 可以保证你的网站样式完整，同时又能保持 CSS 文件的大小。]]></description>
            <content:encoded><![CDATA[<p>最近我在某个开发群里看到有人求助，说自己的 Tailwind css 类没有生效，于是我就看到了他的代码，他的 Vue 代码里使用了 ref 保存数字，动态组合成 Tailwind css 的类名 <code>w-4</code>到 <code>w-9</code>，最后仅 <code>w-9</code>没有生效。</p>
<p>很显然，这个问题我立马猜测这是没配置 <code>safelist</code>，所以打包后没有生成 <code>w-9</code>的类，最后排查也是如此。</p>
<p>本文稍微记录一下这个问题，然后谈谈如何使用 <code>tailwind css</code>的 <code>safelist</code>去支持动态类名。</p>
<h2>前置知识 - Tailwind css</h2>
<blockquote>
<p>传送门 <a href="https://tailwindcss.com/">Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.</a></p>
</blockquote>
<p>Tailwind CSS 上一个流行的实用性优先的 CSS 框架，允许开发者通过快速定义类名的方式，动态生成 CSS 代码，并且还提供了大量实用的基础类。</p>
<p>说得直白一点，Tailwind CSS 可以看作是一个工具箱，里面装满了各种各样已经预先定义好的 CSS 样式。每个样式都对应着一个特定的 CSS 属性，比如颜色、大小、间距等等。你在写 HTML 代码的时候，可以直接使用这些样式，而不用自己去写 CSS 代码。</p>
<p>也就是说，你不用花心思成为起名大师，也不会写重复或后续不会使用的 CSS 代码。Tailwind CSS 的样式还具有统一性，可以保证你的页面风格尽可能一致。</p>
<p>使用 <code>tailwind css</code>可以在项目构建的时候生成较小的 <code>css</code>文件。</p>
<p>当然，吐槽 <code>tailwind css</code>的人会说：</p>
<ul>
<li>类名样式写在 HTML 中，类名会非常长</li>
<li>需要学习 Tailwind css 提供的各种类名</li>
</ul>
<p>这个观点也不无道理，但是技术就是如此，如果你能接受它的缺点，那么就可以更好地享受它的优点。</p>
<h2>Safelist</h2>
<blockquote>
<p>以下分享的知识源于笔者阅读官方文档后的感悟，感兴趣的可以自查</p>
</blockquote>
<p>在 <code>tailwind css v3.x</code> 中 Safelist 即安全列表，用于指定构建的 <code>css</code>文件中始终包括对应的类。当你需要 tailwind css 生成 css 的时候生成一些项目代码中不涉及的类名（有可能是第三方库或组件的类名），或者是动态由 JavaScript 生成的类名，甚至是条件渲染容易缺失和遗漏的类名的时候非常有用。</p>
<p>举个例子，你的 CMS 系统将用户的某些样式设置为某个类名，这部分是你的项目代码中不存在的类名，而你依然想用 tailwind css 在生成的时候生成对应的样式。
亦或是，你通过 JavaScript 去动态添加对应的类名，这部分类名可能不存在于项目代码扫描的内容里。</p>
<p>在 <code>tailwind css v3.x</code> 中，你可以在 <code>tailwind.config.js</code>这样的配置文件里设置白名单：</p>
<pre><code class="language-js">module.exports = { 
	content: [ 
	// your content files here 
	], 
	safelist: [ &#39;w-9&#39;, &#39;columns-10&#39;, &#39;...&#39; ], 
	// other configurations 
};
</code></pre>
<p>在打包构建的时候，最终的 tailwind css 产物里会包含这些白名单的类名。</p>
<p>当然，如果你有大量同类型的类名，可以有更简单的方法配置，例如使用正则表达式和内容变体：</p>
<pre><code class="language-js">/** @type {import(&#39;tailwindcss&#39;).Config} */ module.exports = { 
	content: [
		&#39;./pages/**/*.{html,js}&#39;, 
		&#39;./components/**/*.{html,js}&#39;, 
	],  
	safelist: [ 
		&#39;text-2xl&#39;, &#39;text-3xl&#39;, 
		{ pattern: /bg-(red|green|blue)-(100|200|300)/, }, 
		{ 
			pattern: /bg-(red|green|blue)-(100|200|300)/, 
			variants: [&#39;lg&#39;, &#39;hover&#39;, &#39;focus&#39;, &#39;lg:hover&#39;], 
		},
	],
	 // ... 
}
</code></pre>
<h2>最佳实践</h2>
<p>当你使用 safelist 的时候，注意一下几点：</p>
<ul>
<li>仅在 safelist 中添加真正需要的类名</li>
<li>如果你有大量类名需要添加，则考虑使用正则表达式和变体配置让代码更简洁，更易于维护</li>
<li>在上线之前，测试生成的样式表里是否真正包含你预期的样式，以免最后返工加班 🤣</li>
</ul>
<h2>最后谈谈 tailwind css v4.x</h2>
<p>在 2025年1月22日, tailwind 官方推出 v4 版本更新，在新版本中 safelist 的介绍篇幅大减，当你在官网搜索这个关键字的时候，仅能找到以下内容：</p>
<p>在 css 中使用 @config 指令来载入旧版本的配置文件</p>
<pre><code class="language-css">@config &quot;../../tailwind.config.js&quot;;
</code></pre>
<blockquote>
<p>The <code>corePlugins</code>, <code>safelist</code> and <code>separator</code> options from the JavaScript-based config are not supported in v4.0.</p>
</blockquote>
<p>如上所示，不支持 v4.0 中基于 js 配置文件的 safelist 选项了。</p>
<p>项目维护者对这个问题的回复如下：</p>
<blockquote>
<p><strong>Not solved yet but something we&#39;re going to look into soon. I secretly wish no one used this feature though 😄</strong>
还没搞定，但是我们会尽快看看这个功能，我希望不会有人使用这个功能 😁
2024 年 9 月 20 日</p>
</blockquote>
<p>至今，官网任然没有提供新的更新来解答此问题（网上存在特殊的解法），走着瞧。</p>
<p>此外，🤣 有人对这个问题的解法是使用行内 <code>style</code>属性，成功收获十个开发者的支持 👎</p>
<h2>总结</h2>
<p>safelist 是 Tailwind CSS v3 中一个非常有用的功能，可以帮助你解决一些特殊情况下类名无法被检测到的问题。合理使用 safelist 可以保证你的网站样式完整，同时又能保持 CSS 文件的大小。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[【译】RESTful API 最佳实践]]></title>
            <link>https://i5lin.top/blog/post/2848679579</link>
            <guid isPermaLink="false">2848679579</guid>
            <pubDate>Wed, 12 Feb 2025 15:57:11 GMT</pubDate>
            <description><![CDATA[原文：《REST API Design Best Practices》，本文内容为笔者翻译以及自我理解的分享。]]></description>
            <content:encoded><![CDATA[<p>原文：<a href="https://tigerabrodi.blog/rest-api-design-best-practices?ref=dailydev">REST API Design Best Practices</a>，本文内容为笔者翻译以及自我理解的分享。</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/607bd4713f0df4618dcec5dbf883d9b8.jpg" alt="fahim-muntashir-OqOhYRjn_JY-unsplash (1).jpg"></p>
<h2>核心原则</h2>
<p>设计 API 应该：</p>
<ul>
<li>易读且易用</li>
<li>难以误用（每一个接口都是明确的，无歧义）</li>
<li>完整且简洁（不仅仅是项目的需求，还应该适当补全可能的需求，并且不失简洁）</li>
</ul>
<h2>名称和 URL 结构</h2>
<h3>资源名称</h3>
<ul>
<li>使用名词代替资源，而非动词<ul>
<li>Good：<code>/items</code>,<code>/books</code></li>
<li>Bad：<code>/createItems</code>, <code>/getBooks</code></li>
</ul>
</li>
<li>使用复数名称表示集合<ul>
<li><code>/book</code> 和 <code>/books</code></li>
</ul>
</li>
<li>使用破折号以提高可读性（例如，inventory-management）而不是下划线</li>
</ul>
<h3>URL 结构</h3>
<ul>
<li>为嵌套资源实现逻辑分组<ul>
<li>例如：<code>/customers/{id}/orders</code></li>
</ul>
</li>
<li>避免深层次的嵌套<ul>
<li>避免：<code>/users/123/posts/456/comments/789/likes</code>，此类难以看出资源之间的关系</li>
</ul>
</li>
<li>避免在 URL 中透露数据库结构，以免泄露不必要的信息（例如按序生成的 id 或浮标）</li>
</ul>
<h2>版本</h2>
<ul>
<li>永远为接口提供版本开头的路由，以免在破坏性更新的时候增加处理逻辑的复杂性</li>
<li>可选的思路<ul>
<li>地址版本：<code>/v1/book</code>和 <code>/v2/book</code></li>
<li>查询参数：<code>?version=2</code></li>
</ul>
</li>
</ul>
<h2>分页</h2>
<ul>
<li>为大量的数据查询提供分页接口</li>
<li>使用基于 Cursor（光标） 的分页机制（更高效，简单）<ul>
<li>基于 Cursor 的分页机制，使用一个唯一的标识符（如 lastItemId），来标识上一次请求返回的最后一项数据。接下来，服务器通过这个光标来获取下一批数据。</li>
<li>这种分页方式避免了传统分页中可能遇到的跳过或重复数据的问题。比如，如果数据在两次分页请求之间发生变化，使用光标分页可以确保每次都从上一项数据之后继续获取，不会漏掉或者重复返回某些数据。</li>
<li>举例： <code>/items?lastItemId=1000&amp;limit=20</code></li>
</ul>
</li>
</ul>
<h2>筛选和排序</h2>
<ul>
<li>允许通过 URL 查询参数进行筛选<ul>
<li>例子：<code>/users?lastName=Smith&amp;age=30</code></li>
</ul>
</li>
<li>支持动态选择数据的列<ul>
<li>例子：<code>/products?fields=id,name,price</code> （从数据库中读取少量数据，高效）</li>
</ul>
</li>
<li>使用清除参数提供排序逻辑<ul>
<li>例子：<code>/posts?sort=+author,-datePublished</code></li>
</ul>
</li>
</ul>
<blockquote>
<p>注：通过 URL 传输查询参数的方案不够方便，一来 URL 长度有限（依然够长，小问题），二来更重要的是查询参数的类型需要额外的机制去统一序列化，通常前端的数字通过此方式传输到接口层，后端需要处理字符串类型。相对通过 JSON Payload 传输需要额外的统一的序列化机制支持</p>
</blockquote>
<h2>API 操作</h2>
<h3>幂等性 （重点）</h3>
<ul>
<li>在适当的情况下，确保操作是幂等的，即多次相同的请求应产生相同的结果。</li>
<li>对于 <code>DELETE</code>和 <code>PUT</code>操作尤为重要。</li>
<li>对于敏感操作，可以考虑使用幂等性键。例如，Stripe在收费操作中使用了幂等性键。</li>
</ul>
<h3>异步操作</h3>
<ul>
<li>对于耗时较长的操作，使用状态码 <code>202</code>表示操作已被接受但尚未完成。</li>
<li>提供状态端点用于跟踪进度，例如 <code>GET /orders/123/status</code>。</li>
<li>在 <code>Location</code>头部中包含状态端点的URL，帮助客户端了解操作状态的获取位置。这遵循了HATEOAS原则（超媒体作为应用状态的引擎），即API应告知客户端下一步可以做什么。</li>
<li>考虑支持操作取消，例如 <code>DELETE /orders/123/cancel</code>。</li>
</ul>
<h3>部分响应</h3>
<ul>
<li>支持对大型资源（如视频文件）的部分内容检索。这对于不想一次性下载整个文件的情况非常有用，例如Netflix的电影。</li>
<li>客户端可以通过 <code>HEAD</code>请求检查资源大小，然后使用 <code>Range</code>头部请求特定的分块。（不错的技巧，检测资源而不需要获取资源）</li>
</ul>
<h3>安全性</h3>
<ul>
<li><strong>使用SSL/TLS加密</strong>，通过HTTPS保护数据传输。</li>
<li><strong>采用适当的认证和授权机制</strong>，如OAuth、JWT等。</li>
<li><strong>应用速率限制</strong>，以防止拒绝服务（DoS）攻击。</li>
<li><strong>谨慎处理错误消息</strong>，避免泄露敏感信息。应提供清晰的错误消息，说明问题所在以及下一步的操作。</li>
</ul>
<h3>错误处理</h3>
<ul>
<li><strong>返回适当的HTTP状态码</strong>，以准确反映错误类型。</li>
<li><strong>提供清晰的错误消息</strong>，帮助开发者理解问题。</li>
<li><strong>包含足够的调试信息</strong>，但同时避免暴露敏感细节。</li>
<li><strong>对于成功的空响应，使用状态码 <code>204</code></strong>。（通常后端会返回 200 😂）</li>
</ul>
<blockquote>
<p>题外话，我曾经思考过前端请求中的拦截器在返回响应数据之前应该如何处理服务端状态码。
我认为应该统一使用规范的状态码，避免使用自定义状态码（在返回的 JSON 中定义 status code）。
在某次群组讨论中，有人提出应该在拦截器中既处理常规服务端异常状态码，也要在应用的返回 JSON 中提供应用级别的状态码。</p>
</blockquote>
<p>转移一下话题，让 AI 来分析一下第二种做法：在 HTTP 响应中使用标准的 HTTP 状态码，同时在响应体中提供应用级状态码（如 data.code）有几个明显的好处：</p>
<p><strong>1. 分离关注点，增强灵活性</strong></p>
<p>• <strong>HTTP 状态码负责协议级别的错误处理</strong>，例如，网络错误、未授权、服务器错误等，这些问题通常与请求的传输和处理过程有关。而 <strong>应用级状态码（如</strong> data.code**）则专注于应用逻辑层的状态**，比如操作成功、数据验证失败、权限不足等。</p>
<p>• 通过分离这两者，你可以在 HTTP 协议层面保持标准化，同时在应用层提供更细粒度的控制。这使得前端能更精确地响应不同的应用级别错误，而不至于因为协议层的状态码而混淆业务逻辑。</p>
<p><strong>2. 清晰的错误处理</strong></p>
<p>• 使用标准的 HTTP 状态码可以保证大多数情况都能通过简单的检查处理。例如，4xx 状态码就能快速告诉你是否是客户端请求错误，5xx 则提示服务器故障。</p>
<p>• 但对于更复杂的错误或业务逻辑层的错误，<strong>应用级状态码可以提供更多信息</strong>，如表单字段错误、用户权限问题、请求失败的具体原因等。前端可以通过这些状态码精准控制显示错误信息或者执行相应的操作，而不必依赖于模糊的 HTTP 状态码。</p>
<p><strong>3. 提升可扩展性</strong></p>
<p>• 如果你的应用有较复杂的业务逻辑，使用标准的 HTTP 状态码可能无法涵盖所有业务场景。比如，一个登录接口可能返回 200 状态码，但需要根据 data.code 来判断是登录成功还是账号被锁定。</p>
<p>• 应用级状态码使得你能够定义更加细化的业务逻辑处理流程。随着应用规模增长，增加新的应用级状态码也会变得更加清晰和有序。</p>
<p><strong>4. 用户体验的优化</strong></p>
<p>• 前端可以通过 data.code 做出非常细粒度的用户提示和引导，避免过度依赖 HTTP 状态码。例如：
• 200 状态码表示请求成功，但可以通过 data.code 判断是否需要用户做出进一步操作（如修改某些字段）。
• 401 状态码表示未授权，但 data.code 可能告诉用户“您的账户已过期”或“您的权限不足”。
• 这种细节上的处理能极大地提升用户体验，减少因信息不充分而导致的困惑。</p>
<p><strong>5. 便于国际化与本地化</strong></p>
<p>• 通过应用级状态码返回的错误信息可以与用户语言和地区相关联。你可以根据 data.code 和对应的消息字段来返回本地化的错误信息，而无需依赖 HTTP 状态码本身。</p>
<p>• 这种方式也能让你方便地为用户提供定制化的解决方案，例如，依据 data.code 提供不同的帮助文档链接或者操作指南。</p>
<p><strong>6. 可调试性和日志记录</strong></p>
<p>• 使用应用级状态码可以让开发者在日志中记录更具体的错误信息，有助于定位和排查问题。比如，可以在日志中明确标记是由于数据验证失败、权限问题还是某个特定业务条件未达成而导致的错误。</p>
<p>这样，你不仅能在后端更好地跟踪错误发生的原因，也能在前端做针对性的调试和处理。</p>
<p>我被说服了。</p>
<h2>文档</h2>
<blockquote>
<p>文档是重中之重，提供设计文档甚至优先于提供测试接口</p>
</blockquote>
<ul>
<li>**使用OpenAPI（原Swagger）**进行API文档编写。</li>
<li><strong>文档内容应包括</strong>：<ul>
<li>端点结构。</li>
<li>请求和响应格式。</li>
<li>认证要求。</li>
<li>错误代码和消息。</li>
</ul>
</li>
</ul>
<h2>HATEOAS （超媒体作为应用状态的引擎）</h2>
<blockquote>
<p><strong>HATEOAS (Hypermedia as the Engine of Application State)</strong> 是一种 RESTful API 的设计理念，它是 REST 架构风格的一个重要特性之一。HATEOAS 强调“超媒体作为应用程序状态的引擎”，也就是说，客户端不仅能获取资源的数据，还能通过从资源响应中获得的超链接来进一步了解该资源及其操作。</p>
</blockquote>
<p>HATEOAS 主要是通过返回动态生成的超链接（links）来引导客户端如何进行下一步的操作，而不仅仅是返回数据。客户端通过这些超链接可以根据当前资源状态，知道能够进行哪些操作。</p>
<p>举个例子：
假设我们有一个用户资源，客户端请求一个特定的用户（例如 /users/1），返回的响应可能如下：</p>
<pre><code class="language-json">{
  &quot;id&quot;: 1,
  &quot;name&quot;: &quot;Alice&quot;,
  &quot;email&quot;: &quot;alice@example.com&quot;,
  &quot;links&quot;: [
    {
      &quot;rel&quot;: &quot;self&quot;,
      &quot;href&quot;: &quot;/users/1&quot;
    },
    {
      &quot;rel&quot;: &quot;update&quot;,
      &quot;href&quot;: &quot;/users/1/update&quot;
    },
    {
      &quot;rel&quot;: &quot;delete&quot;,
      &quot;href&quot;: &quot;/users/1/delete&quot;
    },
    {
      &quot;rel&quot;: &quot;friends&quot;,
      &quot;href&quot;: &quot;/users/1/friends&quot;
    }
  ]
}
</code></pre>
<p>在这个例子中：</p>
<p>• rel 属性描述了链接的关系，告诉客户端该链接所代表的操作是什么。例如，“self”表示获取当前资源的 URL，“update”表示更新用户信息的 URL。</p>
<p>• href 属性提供了实际的 URL，客户端可以根据这些链接进行进一步操作。</p>
<blockquote>
<p>😄 长见识了，如上所示：传统的 REST API 可能要求客户端在请求中使用硬编码的 URL，或者客户端自己在多个请求之间维护操作顺序。而 HATEOAS 通过动态链接提供了明确的导航，使得客户端不需要依赖硬编码的 URL 结构。</p>
</blockquote>
<p>我喜欢这种概念，非常灵活。</p>
<p>以上就是今天分享的内容，希望大家喜欢。</p>
<p>Bye.</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.70]]></title>
            <link>https://i5lin.top/blog/post/2841057536</link>
            <guid isPermaLink="false">2841057536</guid>
            <pubDate>Mon, 10 Feb 2025 00:29:24 GMT</pubDate>
            <description><![CDATA[本期主要分享了多个实用工具和资源，这些工具涵盖了开发、设计、AI 等多个领域，适合开发者和设计师使用参考。其中特别推荐 Sonner 的动画效果和流畅阅读的翻译功能。]]></description>
            <content:encoded><![CDATA[<p>Hello，大家好。本周给大家带来的一些讯息如下：</p>
<p>本期内容主要包括以下几个方面：</p>
<ul>
<li>🔧 开发工具：介绍了 Sonner（React Toast组件）、Prisma 数据库服务等开发工具</li>
<li>🛠️ 实用工具：分享了 Look Scanned（扫描效果工具）、HiPixel（图像超分辨率工具）、Kimi Copilot（网页总结工具）等</li>
<li>🎨 设计资源：推荐了 UI Verse（UI组件库）和 Handheld Design（移动端设计灵感）</li>
<li>🤖 AI 相关：介绍了 Exa AI Search、DeepSeek 提示库等 AI 工具</li>
<li>🌐 浏览器插件：分享了流畅阅读（AI翻译）和侧边书签栏等实用插件</li>
</ul>
<p>这些工具和资源涵盖了开发、设计、AI 等多个领域，适合开发者和设计师参考使用。</p>
<h2>Sonner</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/e314fa2042bf3c83030626a334797b27.png" alt="image.png"></p>
<p><a href="https://sonner.emilkowal.ski/" title="Sonner">Sonner</a>是一个 React Toast 组件，这是我目前为止最喜欢的 Toast 组件，因为他的动画做得实在是深得我心！</p>
<p>此前我推荐过 React-hot-toast 和 Shadcn UI 的 Toast，二者各有各的特色。但我个人感觉 Sonner 是二者之间的平衡。Sonner 兼具优秀的交互体验和灵活的 API，尤其是多 Toast 时的动画效果实在是深得我心，必须放在本周的推荐首列！</p>
<p>ps：作者是真正的超级开发者 + 天才设计师！</p>
<h2>Prisma 数据库</h2>
<blockquote>
<p>Prisma 是一个功能强大的数据库工具箱，可以帮助开发者更轻松地管理和使用数据库。</p>
</blockquote>
<p>除了作为我们常用的数据库 ORM 工具，他们在近期还推出了兼容 Postgresql 的数据库服务，以下是价格表：</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/8b917fb56e8e6c1ad31f2d3c194e0114.png" alt="image.png"></p>
<p>免费账户的内容大致是：最初的10万次操作是免费的，每一个数据库支持免费的 1GB 存储，最多十个数据库。</p>
<p>📢 平台没有提供预算控制，因此需要自己注意用量和费用，同时也不支持备份。此外还提供五次免费的 AI 数据库分析后的优化建议。</p>
<blockquote>
<p>📢 刚推出的产品大多数都会出现一些问题（经验之谈），但是这个服务还是很适合熟悉 Prisma 技术栈的独立开发者，推荐了解一下。</p>
</blockquote>
<h2>Look Scanned</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/41368cdd3841af7e1830b6f5078d4f52.png" alt="image.png"></p>
<p><a href="https://lookscanned.io/en/" title="Look Scanned - Scan PDF to Look Scanned - Fast, Online, Free">Look Scanned - Scan PDF to Look Scanned - Fast, Online, Free</a> 是一个开源免费的离线工具，可以借助浏览器的能力，将图片、PDF 文件等材料渲染成类似扫描件的效果。</p>
<p>某种意义上来说，如果你需要一份扫描件却不想去打印店，你也没有扫描仪，这时候这个工具倒是有点用处。</p>
<p>看看效果：
<img src="https://de4965e.webp.li/blog-images/2025/02/fef90c13700e7fd8e36ec45a1d409cbd.png" alt="image.png"></p>
<p>ps：我想要扫描件效果的素材图作为产品介绍，也可以发挥作用。</p>
<h2>HiPixel</h2>
<blockquote>
<p>官网地址：<a href="https://github.com/okooo5km/HiPixel?tab=readme-ov-file#%E4%B8%AD%E6%96%87" title="okooo5km/HiPixel: HiPixel is a native macOS application for AI-powered image super-resolution, built with SwiftUI and leveraging Upscayl&#39;s powerful AI models.">okooo5km/HiPixel: HiPixel is a native macOS application for AI-powered image super-resolution, built with SwiftUI and leveraging Upscayl&#39;s powerful AI models.</a></p>
</blockquote>
<p>HiPixel 是一款原生 macOS 应用程序，用于 AI 图像超分辨率处理，使用 SwiftUI 构建，并采用 Upscayl 的强大 AI 模型。</p>
<p>✨ 功能特点</p>
<ul>
<li>🖥️ 原生 macOS 应用程序，使用 SwiftUI 界面</li>
<li>🎨 使用 AI 模型进行高质量图像放大</li>
<li>🚀 GPU 加速，处理速度快</li>
<li>🖼️ 支持多种图像格式</li>
<li>💻 现代化直观的用户界面</li>
</ul>
<p>来看看例子，这是我在网上下载的模糊版本的图像：
<img src="https://de4965e.webp.li/blog-images/2025/02/e15dfcbd2f8b1386e9896ef1ef396c25.jpg" alt="image2.jpg">
接下来我们用 HiPix 处理一下，得到一份四倍高清图：</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/af661de0b46d5b4c150a373c8b43efe9.png" alt="image2_hipixel_4x_upscayl-standard-4x.png"></p>
<p>在我的显示器上看，效果非常显著。
另外 Zipic 也是这个作者开发的，如果你不需要这么高的分辨率，可以考虑使用内置的 Zipic 联动压缩。</p>
<h2>UI Verse</h2>
<p>近乎 6000 个组件，免费供个人和商业使用，复制代码到你的项目就可以。</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/7b6ebd17b887f8ee20c3cd60e65cad1b.png" alt="image.png"></p>
<p><a href="https://uiverse.io/" title="Uiverse | The Largest Library of Open-Source UI elements">Uiverse | The Largest Library of Open-Source UI elements</a> 是一个大型的开源 UI 组件分享站点，用户可以在这里轻松找到自己满意的组件。</p>
<p>并且，你可以轻松复制到对应组件的原生代码，亦或是对应最流行的前端框架的源码。</p>
<h2>Handheld Design says</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/cec966cee503ee200fdfd78d7943a666.png" alt="image.png"></p>
<p><a href="https://handheld.design/" title="Handheld Design® - Discover best-in-class mobile design inspiration.">Handheld Design® - Discover best-in-class mobile design inspiration.</a> 是一个移动端设计网站，在这里你可以看到非常非常多精美的移动端 APP UI 设计效果，绝对是借鉴和学习的绝佳网站！</p>
<h2>Kimi Copilot - 网页总结助手</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/c746d9b459c4c46c76496ca1582e9f04.png" alt="image.png"></p>
<p><a href="https://chromewebstore.google.com/detail/kimi-copilot-%E7%BD%91%E9%A1%B5%E6%80%BB%E7%BB%93%E5%8A%A9%E6%89%8B/icmdpfpmbfijfllafmfogmdabhijlehn?utm_source=creati.ai" title="Kimi Copilot">Kimi Copilot</a> 是一个 Chrome 浏览器插件，用户只需先登录 Kimi 账号即可在任何网页上调用这个工具去总结页面内容，与此同时还可以自定义提示词，非常灵活。
如果你对总结的内容有更多疑问，还可以继续前往 Kimi 对应的链接进行继续对话。</p>
<h2>Exa AI Search</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/0ad00196c82aee6a66dd0267f546e609.png" alt="image.png"></p>
<p><a href="https://demo.exa.ai/deepseekchat" title="Exa &amp; Deepseek Chat App">Exa &amp; Deepseek Chat App</a> 部署在某国且基于 DeepSeek R1 模型，即 DeepSeek + AI 搜索能力的产品。</p>
<blockquote>
<p>类似开源的工具有 <a href="https://github.com/HarshJ23/Deeper-Seeker" title="HarshJ23/Deeper-Seeker: Deeper Seeker is an simpler OSS version of OpenAI&#39;s latest Deep Research feature in ChatGPT.It is an agentic research tool to reason , create multi step tasks , synthesize data from multiple online resources and create neat reports">HarshJ23/Deeper-Seeker: Deeper Seeker is an simpler OSS version of OpenAI&#39;s latest Deep Research feature in ChatGPT.It is an agentic research tool to reason , create multi step tasks , synthesize data from multiple online resources and create neat reports</a>，用户只需要一些 API Token</p>
</blockquote>
<h2>DeepSeek 提示库</h2>
<p><a href="https://api-docs.deepseek.com/zh-cn/prompt-library" title="Prompt Library | DeepSeek API Docs">Prompt Library | DeepSeek API Docs</a> ，DeepSeek 提供了十三个常见的需求提示示例，包含：</p>
<ul>
<li>代码改写</li>
<li>代码解释</li>
<li>代码生成</li>
<li>内容分类</li>
<li>结构化输出</li>
<li>角色扮演</li>
<li>写作</li>
<li>文档大纲生成</li>
<li>宣传标语生成</li>
<li>提示词生成</li>
<li>中英互译</li>
</ul>
<h2>流畅阅读</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/7de2608c38b022b932fbc27dc4148162.png" alt="image.png"></p>
<p><a href="https://fluent.thinkstu.com/" title="流畅阅读">流畅阅读</a> 是一个 Chrome 浏览器插件，其借助 AI 的能力翻译外语成中文。作者希望让所有人都能够拥有基于母语般的阅读体验。</p>
<h3>核心特性</h3>
<h4>🤖 智能翻译</h4>
<p>基于上下文的智能翻译，采用先进的 AI 技术，提供准确自然的翻译结果，支持多种翻译引擎：</p>
<ol>
<li><p><strong>传统机器翻译</strong></p>
<p>微软翻译、谷歌翻译、DeepL翻译、小牛翻译</p>
</li>
<li><p><strong>AI 大模型翻译</strong></p>
<p><strong>OpenAI</strong>、<strong>DeepSeek</strong>、<strong>SiliconCloud</strong>、Kimi、腾讯混元、字节豆包、阿里通义、智谱清言、OpenRouter、Graq、百度文心、百川智能、零一万物、MiniMax、无向芯穹、阶跃星辰、Claude、Gemini、Ollama 等</p>
</li>
<li><p><strong>自定义引擎</strong></p>
<p>支持自定义 API 接入，灵活扩展第三方大模型翻译服务</p>
</li>
</ol>
<h3>应用场景</h3>
<h4>📚 学术研究</h4>
<ul>
<li>快速阅读外文文献和论文</li>
<li>准确理解专业术语</li>
<li>提高研究效率</li>
</ul>
<h4>💼 工作场景</h4>
<ul>
<li>处理国际商务邮件</li>
<li>阅读技术文档</li>
<li>浏览外文新闻</li>
</ul>
<h4>📖 日常学习</h4>
<ul>
<li>语言学习辅助</li>
<li>跨语言阅读</li>
<li>提升阅读理解能力</li>
</ul>
<p>我使用了谷歌的 Gemini 来翻译，使用体验非常完美。这绝对是一个值得购买的软件，作者细节打磨得很好，我相信会有人买单。</p>
<p>但是，🎉 作者将之开源，感谢！</p>
<h2>侧边书签栏</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/72b63e84757a757c904f144021e8e83c.png" alt="image.png">
<a href="https://bookmarks.mengxianglong.com/" title="侧边书签栏 - 隐私书签、书签加密同步和分享">侧边书签栏 - 隐私书签、书签加密同步和分享</a> 是 <a href="https://x.com/mxlg2003" title="mxlg2003 (@mxlg2003) / X">mxlg2003 (@mxlg2003) / X</a> 开发的一款 chrome 插件，可以让用户在侧边栏管理自己的书签。也可以将部分书签隐藏起来，通过密码访问。此外还具有一系列实用功能，感兴趣的朋友可以了解一下。</p>
<h2>笑</h2>
<p>编程</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/f9bfe371fedccf50f7867d45ce4572b5.png" alt="image.png"></p>
<p>版本管理</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/5294b67e4b4096c0bde219ec8460a3f2.png" alt="image.png"></p>
<p>猫
<img src="https://de4965e.webp.li/blog-images/2025/02/6411a494495bae6820b26c50fc448a86.png" alt="image.png"></p>
<h1>最后</h1>
<p>最近<del>开始准备</del>一直在找远程工作，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
<p>对了，来分享一下对象烤的东西：
<img src="https://de4965e.webp.li/blog-images/2025/02/4b2ddec6b705810bf718d0d5e01ce22c.png" alt="image.png"></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/2e76fdae4c16b71d622e4b2f2a126157.png" alt="image.png"></p>
<p>很不错对吧，哈哈哈哈！</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.69]]></title>
            <link>https://i5lin.top/blog/post/2831151996</link>
            <guid isPermaLink="false">2831151996</guid>
            <pubDate>Tue, 04 Feb 2025 19:48:33 GMT</pubDate>
            <description><![CDATA[某个平静的午后，你做完了人生中最后一次这个动作 😭]]></description>
            <content:encoded><![CDATA[<p>OK！继续来分享上一周接触到的一些讯息！</p>
<p>本文主要分享了以下内容：</p>
<ul>
<li><strong>硅基流动 (SiliconFlow)</strong> - 一个提供多种大模型API服务的云平台，价格具有竞争力</li>
<li><strong>Cherry Studio</strong> - 开源AI客户端，支持多服务商接口和本地数据存储</li>
<li><strong>Amarok</strong> - 安卓免Root隐藏文件和应用的工具</li>
<li><strong>MPMD</strong> - 简洁的微信公众号Markdown编辑器</li>
<li><strong>开发工具推荐</strong> - Screenity(录屏工具)、Modern screenshot(截图库)、React Hook Form(表单库)、React Hot Toast(通知组件)</li>
<li><strong>Daily.dev</strong> - 开发者技术内容聚合平台</li>
<li><strong>Mac安全工具</strong> - Little Snitch和LuLu两款网络监控防火墙软件</li>
</ul>
<p>此外还分享了MacOS文本多选的小技巧，以及一些实用的React动画组件资源。</p>
<h2>硅基流动 Siliconflow</h2>
<p><a href="https://siliconflow.cn/zh-cn/">https://siliconflow.cn/zh-cn/</a></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/928bb9704d06f0fd4273d49d86514432.png" alt=""></p>
<p>硅基流动（SiliconFlow）是一家致力于构建可扩展、标准化和高性能的生成式人工智能计算基础设施平台的公司。</p>
<p>如果你想找一个一站式的大模型服务云平台，将某种大模型 API 接入到自己的产品中，那么推荐了解一下这家公司的服务。</p>
<p>该平台基于优秀的开源基础模型，如Qwen、DeepSeek、GLM、Yi、Mistral、LLaMA 3、SDXL、InstantID等，用户可以自由切换适合不同应用场景的模型。相同的开源模型的推理 api 服务，SiliconCloud 提供的服务基本上是行业最低价。</p>
<blockquote>
<p>某些 API 需要用户注册并且进行实名</p>
</blockquote>
<p>这个平台提供了市面上流行的大多数大模型接口，其中大部分的价格都非常低，同时提供一部分大模型的免费接口。</p>
<p>此外，官网提供了 playground，可以免费体验所有 文本对话/文生图/图生图/文生视频 等 ai 服务。</p>
<p>现在注册，可以获得 2000w token，欢迎使用我的邀请链接：<a href="https://cloud.siliconflow.cn/i/htWlAep9">https://cloud.siliconflow.cn/i/htWlAep9</a></p>
<p>扫描二维码也行：</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/4888f509e048ef899648391405ceeea2.png" alt=""></p>
<h2>Cherry Studio</h2>
<p><a href="https://cherry-ai.com/">https://cherry-ai.com/</a></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/5b215edd98548eb1f4a92247f0b7854f.png" alt=""></p>
<p>一款开源的 AI 客户端，支持多服务商接口，数据完全本地存储，还支持了知识库功能。</p>
<p>你可以通过上述硅基流动这样的平台提供的 API Token 来使用这个客户端，同时也可以选择它提供的多种预设 AI 助手。</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/0bb02d21a48bf40588385a1e88c9177f.png" alt=""></p>
<h2>Amarok</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/2ac06d368f33d7378bc4d495e4bc9e7f.png" alt=""></p>
<p><a href="https://deltazefiro.github.io/Amarok-doc/">Amarok</a> 是一个安卓 APP，支持免 Root 一键隐藏文件和应用。</p>
<blockquote>
<p>如果是苹果用户，可以了解一下 AALocker 这款 APP。基于家长控制来隐藏和限制手机上的 APP。</p>
</blockquote>
<p>效果如图所示：</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/3cadbd653b0482a072e34a165be321a4.png" alt=""></p>
<h2>公众号编辑器</h2>
<p><a href="https://mpmd.pages.dev/">MPMD</a> 是一款高度简洁的微信 Markdown 编辑器。</p>
<p>这个产品特点如下：</p>
<ul>
<li>提供丰富的排版样式，轻松打造精美图文</li>
<li>所见即所得，实时查看最终效果</li>
<li>快速生成代码，一键复制到微信公众号</li>
<li>内置支持主流图片托管平台，粘贴即可上传并在Markdown中展示，集成官方微信公众号图片托管服务免费存储图片，无需额外付费，也无需处理公众号引用第三方图片时失败的问题</li>
</ul>
<p>支持 Chrome 、Edge 以及 FireFox 浏览器。</p>
<blockquote>
<p>如果你对源代码感兴趣，也可以访问：<a href="https://github.com/honwhy/md">https://github.com/honwhy/md</a></p>
</blockquote>
<h2>Screenity</h2>
<p><a href="https://github.com/alyssaxuu/screenity?tab=readme-ov-file">alyssaxuu/screenity: The free and privacy-friendly screen recorder with no limits 🎥</a></p>
<p>又一个免费的浏览器录屏插件。</p>
<h2>Modern screenshot</h2>
<p><a href="https://github.com/qq15725/modern-screenshot">https://github.com/qq15725/modern-screenshot</a></p>
<p>又一款前端轮子，基于 Canvas API 针对浏览器 Node 生成图片。</p>
<p>来看看代码示例：</p>
<pre><code class="language-jsx">import { domToPng } from &#39;modern-screenshot&#39;

domToPng(document.querySelector(&#39;#app&#39;)).then(dataUrl =&gt; {
  const link = document.createElement(&#39;a&#39;)
  link.download = &#39;screenshot.png&#39;
  link.href = dataUrl
  link.click()
})
</code></pre>
<p>此外，这个库还能通过重用上下文和 web worker 每秒快速截图。</p>
<p>PS：这类的库原理大多一致，建议稍微了解一下即可。</p>
<p>吐槽：这么多年了浏览器还是没有原生支持这个需求的接口。。。</p>
<h2>React Hook Form</h2>
<p><a href="https://react-hook-form.com/">https://react-hook-form.com/</a></p>
<p><strong>React Hook Form</strong> 是一个用于在 React 中处理表单状态的库。它基于 React 的 Hooks API，帮助你轻松管理表单数据、验证和错误处理。</p>
<p><strong>特点与优点：</strong></p>
<ol>
<li><strong>高性能</strong>：减少不必要的重新渲染，表单处理更流畅。</li>
<li><strong>轻量级</strong>：核心库体积小，加载速度快。</li>
<li><strong>易于集成</strong>：可以与任意 UI 库（如 MUI、Ant Design）无缝配合使用。</li>
<li><strong>内置验证</strong>：支持原生验证规则，也能与 Yup、zod 等库集成。</li>
<li><strong>简洁 API</strong>：使用 <code>register</code>、<code>handleSubmit</code>、<code>watch</code> 等 API，代码更简洁易读。</li>
</ol>
<p><strong>为什么推荐：</strong></p>
<ul>
<li>适合复杂表单场景，性能优异。</li>
<li>学习成本低，快速上手。</li>
<li>易于扩展，适合长期维护的项目。</li>
<li>在 Web 和 React native 上表现一致，非常适合使用 React 技术栈开发 web 和 APP 的开发者</li>
</ul>
<h2>React hot toast</h2>
<p><a href="https://react-hot-toast.com/">https://react-hot-toast.com/</a></p>
<p><strong>React Hot Toast</strong> 是一个用于在 React 应用中展示通知（Toast）的轻量级库，提供简单且高效的 API 来快速创建美观的消息提示。</p>
<p><strong>优点：</strong></p>
<ol>
<li><strong>极简 API</strong>：只需 <code>toast()</code> 函数即可快速调用，无需复杂配置。</li>
<li><strong>高性能</strong>：渲染速度快，动画流畅，适合频繁触发通知的场景。</li>
<li><strong>可定制性强</strong>：支持自定义样式、位置、动画效果，轻松适配不同需求。</li>
<li><strong>支持异步操作</strong>：内置 <code>toast.promise</code>，方便处理异步请求的加载、成功、失败状态。</li>
<li><strong>零依赖</strong>：体积小，不依赖其他库，保持应用轻量化。</li>
</ol>
<p><strong>推荐理由：</strong></p>
<ul>
<li>适合快速开发，简单易用。</li>
<li>动画与交互自然，提升用户体验。</li>
<li>高度可定制，适配各种 UI 风格。</li>
<li>适合替代复杂的通知系统，减少代码冗余。</li>
</ul>
<h2>Daily.dev</h2>
<p><a href="https://app.daily.dev/">daily.dev | Where developers grow together</a></p>
<p><strong>daily.dev</strong> 是一个专为开发者打造的技术内容聚合平台，能帮你快速获取全球最新的开发资讯、技术文章和行业动态。</p>
<p><strong>推荐理由：</strong></p>
<ul>
<li><strong>省时省力</strong>：不用再打开无数个网站，精选的开发者文章总能让你收获到新的知识。</li>
<li><strong>提升效率</strong>：碎片时间快速充电，保持对新技术的敏感度。</li>
</ul>
<h2>React 动画组件资源</h2>
<p><a href="https://github.com/DavidHDev/react-bits">https://github.com/DavidHDev/react-bits</a></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/dfb28079dafffa7dd426f872b691e2fb.png" alt=""></p>
<p>令人震惊的 react 生态，这些组件包含：</p>
<ul>
<li>文本动画</li>
<li>背景动画</li>
<li>常见组件</li>
</ul>
<p>实际效果非常好，要实现这些效果需要花很多心思，感谢 React 开源社区和开发者。</p>
<blockquote>
<p>这是作者的博客：<a href="https://davidhaz.com/#">https://davidhaz.com/#</a></p>
</blockquote>
<h2>Little Snitch</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/d1a2f55429fea8db222df2d912419e67.png" alt=""></p>
<p><a href="https://www.obdev.at/products/littlesnitch/index.html">Little Snitch — Network Monitor and Application Firewall for macOS</a></p>
<p>小偷网络监控软件能显示你的Mac连接到互联网的地点。你自己决定想允许还是拒绝什么。</p>
<p>说人话就是：MacOS 系统上的网络监控软件，可以实时查看网络通过什么软件传输到了什么地方。</p>
<p>你可以用这个软件来屏蔽一些本机上的应用传输数据到某个你不想要传过去的地方，比如禁止搜狗输入法联网。</p>
<p>如果你想要支持正版，可以去数码荔枝购买~</p>
<p><a href="https://lizhi.shop/site/products/id/176">https://lizhi.shop/site/products/id/176</a></p>
<h2>LuLu</h2>
<p><a href="https://objective-see.org/products/lulu.html">Objective-See: LuLu</a></p>
<p>LuLu是一款免费、开源的防火墙，旨在阻止未知的<strong>出站连接</strong>，保护用户的隐私和Mac设备的安全。</p>
<p>如果你觉得 Little Snitch 太贵了，那么这个开源免费的防火墙软件也不错。</p>
<h2>小技巧</h2>
<p>如何在 MacOS 上多选文本？长按左键拖动到目标内容的末尾？不不不，今天我才知道可以这样：</p>
<ol>
<li>双击开头</li>
<li>长按shift，再左键点击结尾</li>
</ol>
<p>如此即可多选区域内容！</p>
<h2>乐</h2>
<p>😭</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/02/b257ca9f36cf17962c24711b080d7d6b.png" alt=""></p>
<h1>最后</h1>
<p>最近<del>开始准备</del>一直在找远程工作，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.68]]></title>
            <link>https://i5lin.top/blog/post/2797831509</link>
            <guid isPermaLink="false">2797831509</guid>
            <pubDate>Sun, 19 Jan 2025 21:08:18 GMT</pubDate>
            <description><![CDATA[有想法的人多如牛毛，行动者寥寥无几。]]></description>
            <content:encoded><![CDATA[<p>OK！继续来分享上一周接触到的一些讯息！</p>
<p>这期周刊主要分享了以下内容：</p>
<ul>
<li>Z-Library 电子书下载平台的使用体验</li>
<li>AI+Web3 免费公开课介绍，包含多个模块的学习内容</li>
<li>Electerm - 一个跨平台的终端工具，支持多种协议和功能</li>
<li>PeekFocus - 一个浏览器插件，可以在当前页面预览链接内容</li>
<li>Grid 生成器 - 用于生成 CSS Grid 代码的工具</li>
<li>API Farmer - 基于 Swagger 的接口生成工具</li>
<li>Axle - 基于 axios 的网络请求工具</li>
<li>Varletjs - 一个有趣的开源组织及其工具集</li>
<li>GitLeaks - Git 仓库安全检测工具</li>
<li>Plyr - 轻量级媒体播放器</li>
<li>Git 基础知识分享</li>
<li>Cursorful - 浏览器屏幕录制插件</li>
</ul>
<h2>电子书下载 Z-Library</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/01/79958bc95f379a588826a9aa95dd34fc.jpg" alt="https://de4965e.webp.li/blog-images/2025/01/79958bc95f379a588826a9aa95dd34fc.jpg"></p>
<blockquote>
<p>Z-Library 是世界上最大的电子书站点之一，藏书量丰富无比，大多数你在市面上难找的电子书都可以在这里找到。</p>
</blockquote>
<p>我上次领取了一个月的赞助体验券，到月底之前都可以每天下载 15 本电子书，如果你有需要的电子书，可以找我帮忙下载。</p>
<h2>AI+Web3 入门教程</h2>
<p><img src="https://files.mdnice.com/user/9850/8e090643-6942-4157-b201-c8108ccfa8e6.jpg" alt=""></p>
<p>完全免费的公开课：Al+Web3：从理论到实战的进阶之路</p>
<blockquote>
<p>这仅仅是我在网络上看到的资讯，不包含任何推广因素。</p>
</blockquote>
<p>为了帮助更多开发者抓住 <strong>AI+Web3</strong> 的时代机遇，<strong>OpenBuild</strong> 联合 <a href="https://01.ai/" title="**01.AI**"><strong>01.AI</strong></a>, <a href="https://cryptopond.xyz/" title="**Pond**"><strong>Pond</strong></a>, <a href="https://bewater.xyz/" title="**BeWater**"><strong>BeWater</strong></a> 等战略合作伙伴特别推出 <strong>「AI+Web3：从理论到实战开发的进阶之路」</strong> 课程！</p>
<p>用 1h 左右，带大家从 0 入门 langchain</p>
<ul>
<li>langchain 基础</li>
<li>RAG 实现</li>
<li>两种 Agent 实现</li>
</ul>
<p>如果对 AI + Web3 感兴趣，可以去试试。</p>
<p><strong>课程大纲</strong></p>
<ol>
<li><strong>模块一:AI核心概念:</strong> 由 <a href="https://01.ai/" title="**01.AI**"><strong>01.AI</strong></a> 知名讲师分享 AI 基本概念、大模型是如何炼成及其应用</li>
<li><strong>模块二:LLM 应用开发平台:</strong> 由 <a href="https://dify.ai/" title="**Dify.ai**"><strong>Dify.ai</strong></a>、<a href="https://www.langchain.com/" title="**Langchain**"><strong>Langchain</strong></a> 等平台资深开发者介绍 LLM 应用开发</li>
<li><strong>模块三:AI+Web3 项目实践:</strong> 邀请了 <a href="https://cryptopond.xyz/" title="**Pond**"><strong>Pond</strong></a>、<a href="https://gokite.ai/" title="**Kite AI**"><strong>Kite AI</strong></a>、 <a href="https://www.abcde.com/" title="**ABCDE Labs**"><strong>ABCDE Labs</strong></a>、<a href="https://near.ai/" title="**NEAR AI**"><strong>NEAR AI</strong></a>、<a href="https://ao.arweave.net/" title="**AR/AO**"><strong>AR/AO</strong></a>、<a href="https://www.gaianet.ai/" title="**Gaia**"><strong>Gaia</strong></a>、<a href="https://primuslabs.xyz/" title="**Primus**"><strong>Primus</strong></a>、<a href="https://artela.network/" title="**Artela**"><strong>Artela</strong></a>、<a href="https://www.spaceandtime.io/" title="**Space and Time**"><strong>Space and Time</strong></a>、<a href="https://www.covalenthq.com/zh/" title="**Covalent**"><strong>Covalent</strong></a> 等平台资深开发者分享 AI+Web3 实践</li>
</ol>
<p>报名链接如下：<a href="https://t.co/rt9Onvl0yV" title="https://t.co/rt9Onvl0yV">https://t.co/rt9Onvl0yV</a></p>
<h2>Electerm</h2>
<p><img src="https://files.mdnice.com/user/9850/0bb1ea4d-e2d7-4d8c-82fe-d5dc137bff6a.png" alt=""></p>
<p><a href="https://github.com/electerm/electerm?tab=readme-ov-file">https://github.com/electerm/electerm?tab=readme-ov-file</a></p>
<p>这是一个开源终端/ssh/telnet/serialport/RDP/VNC/sftp客户端(linux, mac, win)。</p>
<p><strong>功能特性</strong></p>
<ul>
<li>支持 SSH、Telnet、SerialPort、RDP、VNC、本地和远程文件管理、SFTP 文件传输，以及作为本地终端使用</li>
<li>全局快捷键切换隐藏显示窗口（类似 Guake，默认快捷键 <code>ctrl + 2</code>）</li>
<li>多平台支持（Linux、Mac、Win）</li>
<li>🇺🇸 🇨🇳 🇧🇷 🇷🇺 🇪🇸 🇫🇷 🇹🇷 🇭🇰 🇯🇵 🇸🇦 🇩🇪 🇰🇷 多国语言支持（<a href="https://github.com/electerm/electerm-locales" title="electerm-locales">electerm-locales</a>，欢迎提交代码）</li>
<li>双击直接编辑远程文件</li>
<li>支持密码或者密钥登录</li>
<li>支持 Zmodem（rz、sz）</li>
<li>支持 SSH 隧道</li>
<li>支持 <a href="https://github.com/trzsz/trzsz" title="Trzsz">Trzsz</a>（trz/tsz），类似 rz/sz，兼容 tmux</li>
<li>支持透明窗口（Mac、Win）</li>
<li>支持设置终端背景图片</li>
<li>支持代理服务器</li>
<li>支持预设快捷命令</li>
<li>支持主题</li>
<li>支持同步书签等数据到 GitHub/Gitee 私人 Gist</li>
<li>支持快速输入命令到一个或者多个终端</li>
<li>支持命令行使用：请参阅 <a href="https://github.com/electerm/electerm/wiki/Command-line-usage" title="wiki">wiki</a></li>
<li>深度链接支持：请参阅 <a href="https://github.com/electerm/electerm/wiki/Deep-link-support" title="wiki">wiki</a></li>
</ul>
<h2>Peek Focus</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/01/508bb6d05712ab00ae41dd82f8c40b7a.png" alt=""></p>
<p><a href="https://peekfoc.us/welcomes">https://peekfoc.us/welcomes</a></p>
<p>PeekFocus 是一个国人开发者的作品，也是一个 Chrome 浏览器的插件。</p>
<p>想象一下，你是否会经常在浏览器搜索一些东西，查看结果的时候打开新的标签页。如此反复之后，标签页变得越来越多。</p>
<p>这个插件提供的最基础的能力就是允许用户通过快捷键或者拖动链接的方式，在当前页面打开一个临时的模拟窗口，在这个窗口里查看链接内容。</p>
<p><img src="https://files.mdnice.com/user/9850/8d88c922-7054-4457-917f-c859d063db84.png" alt=""></p>
<p>此时，你甚至可以选择不同的尺寸来体验不同的页面效果，亦或是尝试阅读模式、在侧边栏单独打开这个内部窗口的内容等功能。</p>
<blockquote>
<p>作者还在开发中，目前可能存在一些 Bug，新的需求和功能也在不断更新。</p>
</blockquote>
<h2>Grid 生成器</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/01/4488cbe8aedd149e3f6ed91517602a9b.png" alt="https://de4965e.webp.li/blog-images/2025/01/4488cbe8aedd149e3f6ed91517602a9b.png"></p>
<p><a href="https://wangmiaozero.github.io/cssgridgenerator-website/" title="CSS Grid Generator">CSS Grid Generator</a> 支持通过选择区域和自定义尺寸的方式，由用户的交互生成 Grid CSS 代码。</p>
<h2>自动接口生成</h2>
<p><a href="https://github.com/varletjs/api-farmer?tab=readme-ov-file" title="varletjs/api-farmer: API module generation tool based on Openapi3/Swagger2.">varletjs/api-farmer: API module generation tool based on Openapi3/Swagger2.</a></p>
<p>这是一个基于Openapi3/Swagger2的API模块生成工具</p>
<p>项目特点：</p>
<ul>
<li>支持从OpenAPI3/Swagger2 schema生成所有API模块</li>
<li>支持生成ts/js模块</li>
<li>提供全面的ts类型生成</li>
<li>支持自定义ejs模板以生成定制内容</li>
<li>允许使用transformer修改模板中的所有变量，实现精细定制</li>
<li>同时支持cli和node.js api</li>
<li>包含axle和axios模板的内置预设</li>
</ul>
<p>使用这个工具，开发者可以通过编写 yaml 配置文件，生成符合 openapi3 的接口文档、请求封装等等代码。</p>
<p>可以在这里查看一些示例：<a href="https://github.com/varletjs/api-farmer/blob/main/fixtures">https://github.com/varletjs/api-farmer/blob/main/fixtures</a></p>
<h2>Axle</h2>
<p><a href="https://github.com/varletjs/axle/tree/main">https://github.com/varletjs/axle/tree/main</a></p>
<p>很有趣的网络请求工具，这个工具基于大名鼎鼎的 <code>axios</code> 进行间接性增强。</p>
<p>类似的工具例如：<code>ky</code> 或者 <code>got</code> 等等，可能会让你对网络请求工具有了一些厌倦。</p>
<p>但是，<code>axle</code> 很有趣。</p>
<p>它完全支持 <code>axios</code> 的配置能力，如果你对 <code>axios</code> 比较熟悉，那么你可以无缝使用这个工具。此外，这个工具考虑了更多实际开发中的一些便捷性操作。</p>
<p>针对不同请求类型的处理很方便，针对常见的浏览器下载、同时获得成功或者失败的响应以及公共请求头的操作等实用功能都很棒。</p>
<p>当然，配合上述接口生成工具 <code>api-farmer</code> 可以更好地发挥这个工具的威力。</p>
<p>尤其是对于 <code>Vue</code> 技术栈来说，这个工具还提供了一个管理请求和数据的钩子，值得我们了解一下。</p>
<p>类似 <code>ky</code> 这样的库对更底层的 <code>fetch</code> 会有一些封装，但是 <code>axle</code> 更进一步，这些功能确实对日常开发有一些帮助，让我们把时间花到其他地方。</p>
<p>或者，休息一下，喝一杯茶。</p>
<h2>Varletjs</h2>
<p><a href="https://github.com/varletjs" title="varletjs">varletjs</a></p>
<p>很有趣的组织，很有趣的名字。</p>
<p><code>varletjs</code> = <code>var</code> + <code>let</code> + <code>js</code> 。</p>
<p>他们有二十三个仓库，每一个都推荐大家看看。</p>
<p>其首页六个固定的仓库如下：</p>
<ul>
<li>verlet 基于Material Design 2和3的Vue3组件库，支持移动端和桌面端。</li>
<li>axle 基于 axios 的渐进式请求工具</li>
<li>rattail 是一个轻量化的、TypeScript 友好的工具库</li>
<li>varlet-app-template 一个基于 <code>vue3</code> 、<code>varlet</code>、<code>Vite</code>、<code>TypeScript</code>开箱即用的移动端应用模版</li>
<li>varlet-release 是一个管理组织所有的包的版本日志、git 提交信息的工具</li>
<li>api-farmer 如上所示，通过 yaml 编写配置文件，快速生成接口请求代码和类型代码的工具</li>
</ul>
<p>如果你使用 <code>Vue+TypeScript</code> 为主的技术栈，那么无论是移动端还是桌面端，这个组织的工具都能够提供很多帮助。</p>
<h2>GitLeaks</h2>
<p><a href="https://github.com/gitleaks/gitleaks" title="https://github.com/gitleaks/gitleaks">https://github.com/gitleaks/gitleaks</a></p>
<p>Gitleaks 是一个用于检测 git 存储库、文件中的密码、API 密钥和令牌以及您想通过 stdin 扔给它的任何其他内容等秘密的工具，通过简单的配置即可保护我们的隐私。</p>
<p>我想几乎所有的仓库都可以配置这个功能。</p>
<h2>Plyr</h2>
<p><a href="https://github.com/sampotts/plyr" title="https://github.com/sampotts/plyr">https://github.com/sampotts/plyr</a></p>
<p>Plyr 是一款简单、轻量级、可访问且可自定义的 HTML5、YouTube 和 Vimeo 媒体播放器，支持现代浏览器。</p>
<p><img src="https://files.mdnice.com/user/9850/d02b3070-d4df-4b1b-8cc9-ddadf9f17f7c.jpg" alt=""></p>
<p>如果你的项目中需要使用播放器相关的内容，那么强烈推荐这个工具。</p>
<p>为什么？因为 YouTube、Vimeo 甚至马斯克的 <a href="http://spacex.com" title="spacex.com">spacex.com</a> 都在使用这个工具，性能极佳且用户体验拉满。</p>
<h2>Git 应用知识分享</h2>
<p><a href="https://www.youtube.com/watch?v=NzMbt7dwI3E">https://www.youtube.com/watch?v=NzMbt7dwI3E</a></p>
<blockquote>
<p>Git 是我们现在最常用的版本管理工具，也是大部份公司的首选。所以建议所有同学都学会。据我观察，很多同学学 GIt 的时候不求甚解，基本就背几个常用的手法，然后每次照本宣科。于是当出现问题的时候，就不知道该怎么解决。</p>
</blockquote>
<p>这是一个关于 Git 操作的 YouTube 视频，作者是一位经验丰富的全栈开发者，经常在网上分享自己的一些技术视频。</p>
<h2>浏览器屏幕录像插件</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/01/241a36d431073ba1beb0b5b31f3267bb.png" alt=""></p>
<p><a href="https://cursorful.com/">https://cursorful.com/</a></p>
<p>这是一个基于浏览器功能的屏幕录像插件，也有人直接将之称为 <code>Chrome 插件版本的 ScreenStudio</code> ！</p>
<p>虽然但是，正版的 ScreenStudio 超贵。</p>
<p>目前这个插件的核心功能都是免费的，作者承诺后续的免费计划的功能会保留一些核心功能，如果你有录制一些演示内容的需求，这个插件不容错过。</p>
<h2>图</h2>
<p>听说现在年轻人不怎么用浏览器和计算机了，考一下大家应该点哪个下载？ 🤣</p>
<p><img src="https://de4965e.webp.li/blog-images/2025/01/ae3f87645df4512e395a2b1ed28656e9.png" alt=""></p>
<p><img src="https://files.mdnice.com/user/9850/a75ee487-44be-4e24-a668-1dd1cf1b1aa9.png" alt=""></p>
<h2>言</h2>
<p>有想法的人多如牛毛，行动者寥寥无几。</p>
<h1>最后</h1>
<p>最近<del>开始准备</del>一直在找远程工作，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.67]]></title>
            <link>https://i5lin.top/blog/post/2783927323</link>
            <guid isPermaLink="false">2783927323</guid>
            <pubDate>Mon, 13 Jan 2025 13:12:21 GMT</pubDate>
            <description><![CDATA[OK！继续来分享上一周接触到的一些讯息！本周将分享涉及 Uniapp echarts 方案、React native CI 自动打包发布、Docker 化无头浏览器、图片压缩 APP、开源的 OG 图片生成应用等等。]]></description>
            <content:encoded><![CDATA[<p><strong>OK！继续来分享上一周接触到的一些讯息！本周将分享涉及 Uniapp echarts 方案、React native CI 自动打包发布、Docker 化无头浏览器、图片压缩 APP、开源的 OG 图片生成应用等等。</strong></p>
<p><img src="https://de4965e.webp.li/blog-images/2025/01/e71b9a9c32c78e4505c0f0d0fb843338.png" alt=""></p>
<h2>Macos 微信双开</h2>
<p><strong>之前我只知道可以用这个分身软件在 MacOS 上双开微信：</strong></p>
<p><img src="https://de4965e.webp.li/blog-images/2024/12/7dbb8e9e9bf3e785028e1c934e2d3ed0.png" alt=""> <a href="https://www.heiweixin.com/">嘿微信 - Mac系统下的微信分身软件</a> ！</p>
<p><strong>但最近发现一种更加“官方”的方案：</strong></p>
<p><img src="https://de4965e.webp.li/blog-images/2024/12/4b513a33612f79cd5112a80ebd10d29f.png" alt=""></p>
<p><strong>在官网直接下载正式版和测试版，同时打开即可！</strong></p>
<h2>react-native-ci</h2>
<blockquote>
<p><strong>感谢 Jimmy 推荐，React Native 开发者欢迎了解一下</strong></p>
</blockquote>
<p><strong>通过 Github Action 对 React Native 代码打包！</strong></p>
<p><a href="https://github.com/Jimmylxue/react-native-ci">Jimmylxue/react-native-ci: 欢迎使用 react-native-ci 项目！本项目旨在提供一个高效的自动化打包解决方案，专为 React Native 应用程序设计。通过简单的配置，您可以快速实现 CI/CD 流程，确保应用的持续集成与交付。</a></p>
<p>**完成配置后，你可以通过 **<code>github-action</code> 完成自动化打包。每当你推送代码、或主动执行打包指令时，系统将自动构建你的 <code>Docker</code> 镜像。完成打包，并将打包结果的 apk 上传至 <code>github-release</code> 中。</p>
<h2>切换按钮的设计方案</h2>
<p><img src="https://pbs.twimg.com/media/Gf_mNvSWIAApu_d?format=jpg&name=large" alt=""></p>
<h2>Beekeeper Studio</h2>
<p><a href="https://www.beekeeperstudio.io/">The SQL Editor and Database Manager Of Your Dreams | Beekeeper Studio</a> 免费功能的 SQLite 数据库 GUI 软件应用，界面美观。</p>
<h2>Docker 无头浏览器</h2>
<p><img src="https://github.com/browserless/browserless/raw/main/assets/logo.png" alt=""></p>
<p><a href="https://github.com/browserless/browserless">browserless/browserless: Deploy headless browsers in Docker. Run on our cloud or bring your own. Free for non-commercial uses.</a></p>
<p><strong>允许远程客户端连接并执行无头浏览器工作，全部在 Docker 内部进行。支持标准的未分叉的 Puppeteer 和 Playwright 库，同时提供基于 REST 的 API 用于常见操作，如数据收集、PDF 生成等。</strong></p>
<h3>项目应用场景</h3>
<ul>
<li><strong>数据收集</strong>：利用无头浏览器进行网页数据抓取。</li>
<li><strong>PDF 生成</strong>：将网页内容生成 PDF 文件。</li>
<li><strong>自动化测试</strong>：进行网页自动化测试，提高测试效率和准确性。</li>
<li><strong>爬虫开发</strong>：作为爬虫工具的一部分，提高爬虫的稳定性和效率。</li>
</ul>
<h2>Niko</h2>
<p><img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/intro.gif" alt=""></p>
<p><a href="https://github.com/m1k1o/neko">m1k1o/neko: A self hosted virtual browser that runs in docker and uses WebRTC.</a> 是一个自托管的虚拟浏览器，运行在 Docker 容器中，使用 WebRTC 技术。</p>
<ul>
<li><strong>特点</strong><ul>
<li><strong>提供一个功能完整的浏览器虚拟环境，可安全、私密地访问互联网。</strong></li>
<li><strong>支持多用户同时访问，适合团队协作或个人多设备使用。</strong></li>
<li><strong>可用于举办线上观影会、互动演示等场景。</strong></li>
</ul>
</li>
<li><strong>适用场景</strong><ul>
<li><strong>开发者测试 Web 应用</strong></li>
<li><strong>隐私意识强的用户</strong></li>
<li><strong>需要虚拟浏览器便利性和灵活性的用户</strong></li>
</ul>
</li>
</ul>
<p><strong>跟上一个无头浏览器类似，如果有多用户同时使用的需求，那么这个比较不错，不过运行 Docker 容器和 WebRTC 流媒体传输对服务器资源有一定要求。</strong></p>
<h2>TinyImage for MacOS</h2>
<p><img src="https://github.com/kyleduo/TinyPNG4Mac/raw/master/preview/banner.png" alt=""></p>
<p>**TinyPNG for macOS 是 **<a href="https://tinypng.com/">TinyPNG</a> 的第三方客户端，使用它，无需打开浏览器即可压缩图片。</p>
<blockquote>
<p><strong>免费服务每个月可以压缩 500 张图片，大多数时候已经足够。</strong></p>
</blockquote>
<h2>微信小程序使用 ECharts</h2>
<p><a href="https://mp.weixin.qq.com/s/LciSFUkOXAK4_dfVUcLJdw">基于uniapp小程序的Echarts最佳解决方案！</a></p>
<h2>开源 OG 图片生成</h2>
<p><img src="https://de4965e.webp.li/blog-images/2025/01/b3468226c020cc1545f09cd60e723dbe.png" alt=""></p>
<p><a href="https://imgsrc.io/">imgsrc</a> 提供了一个简单的方案生成通用 Open Graph 图片以及 X/Twitter 分享图片的解决方案，我的周刊封面也是用它生成的。</p>
<p><strong>此外，这个应用是开源的：</strong><a href="https://github.com/FadyMak/imgsrc-app">FadyMak/imgsrc-app: Generate beautiful Open Graph images</a></p>
<p><strong>如果你也需要开发 Nextjs 应用，那么可以参考一下他人的代码。</strong></p>
<h2>NVIDIA</h2>
<p><img src="https://pbs.twimg.com/media/GgsRtNyakAAOlXs?format=jpg&name=large" alt=""></p>
<ul>
<li><strong>5 月发售</strong></li>
<li><strong>3000$</strong></li>
</ul>
<p><strong>Project Digits  $3000 提供128GB 统一内存，对比Mac mini 选配64GB 统一内存需要 2899刀，换四台5090更是太贵。 用来本地推理+微调看起来是很不错的选择。</strong></p>
<h1>最后</h1>
<p><strong>最近</strong><del>开始准备</del>一直在找远程工作，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p><strong>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</strong></p>
</blockquote>
<p><strong>好了，今天的分享就先到这里，大家下周见~</strong></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[2024 年度总结]]></title>
            <link>https://i5lin.top/blog/post/2764442125</link>
            <guid isPermaLink="false">2764442125</guid>
            <pubDate>Tue, 31 Dec 2024 14:43:04 GMT</pubDate>
            <description><![CDATA[2024 年转瞬即逝，我想在今晚记录一下]]></description>
            <content:encoded><![CDATA[<h1>2024 年，2024 年</h1>
<p><img src="https://de4965e.webp.li/blog-images/2024/12/e03b634d65fe664d1922e33ba6a5c089.png" alt=""></p>
<h2>过去</h2>
<ul>
<li>工作：</li>
</ul>
<pre><code>年初从科达光速离职切换到 Raipiot，年中光速倒闭直接切换为无业游民，年末接了点活和一个两个多月的短期项目。
</code></pre>
<ul>
<li>独立开发</li>
</ul>
<pre><code>上线的插件若干，收费插件：
</code></pre>
<p><strong>0</strong>，收入：<strong>0</strong>。</p>
<ul>
<li><p>公众号维护</p>
<p>同步更新了 22 篇内容，平均每个月仅两篇。</p>
</li>
<li><p>生活</p>
<p>失去了几个朋友的同时，经历了人生最艰难的若干瞬间，也深刻了解并且接受自己的弱点和劣势。</p>
</li>
</ul>
<h2>未来</h2>
<ul>
<li><p>工作</p>
<p>远程最佳，无锡次之。</p>
</li>
<li><p>独立开发</p>
<p>上线若干收费应用，将赚到独立开发的第一笔钱作为目标。👏🏻欢迎有缘人使用我的应用，1 月将会发布第一款 Chrome 书签插件：云岚书签 🔖</p>
</li>
<li><p>公众号维护</p>
<p>每周至少四篇公众号，内容不限，在 2025 年达到 500 关注！</p>
</li>
<li><p>生活</p>
<p>尝试没尝试过的东西，学做菜，完成过去想过却没实现的几件事，去几个一直想去却没去成的地方。对了，改掉熬夜的恶习。</p>
</li>
<li><p>输入</p>
<p>读书 x 5</p>
</li>
</ul>
<h2>最后</h2>
<p>此刻正在从低潮中走出来，感谢那些帮助过我的人，谢谢妈妈、姐姐和象象的支持和鼓励。</p>
<p>现在，笑一下。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.66]]></title>
            <link>https://i5lin.top/blog/post/2762382557</link>
            <guid isPermaLink="false">2762382557</guid>
            <pubDate>Sun, 29 Dec 2024 19:43:28 GMT</pubDate>
            <description><![CDATA[本周将分享涉及 Uniapp 开发、WebChat 即时通讯、React 趋势图、JSON 超集 SuperJSON、云盘和临时点对点传输应用甚至零知识证明的学习资料等等。]]></description>
            <content:encoded><![CDATA[<p>OK！继续来分享上<del>一周</del>（月）接触到的一些讯息！本周将分享涉及 Uniapp 开发、WebChat 即时通讯、React 趋势图、JSON 超集 SuperJSON、云盘和临时点对点传输应用甚至零知识证明的学习资料等等。</p>
<h2>Z-Lib 月会员</h2>
<p>Z-library 推出一个小活动，所有用户可以打开 zlib 首页（自己找，问我也行），点击首屏 banner 的 Gift inside ，找到 unwrap it 文字上面的小船图标，点击即可获取月会员兑换码。</p>
<p><img src="https://pbs.twimg.com/media/Gf9pieXbIAAPHZV?format=jpg&name=4096x4096" alt="Image"></p>
<p><img src="https://pbs.twimg.com/media/Gf9pwkubUAASlk9?format=jpg&name=4096x4096" alt="Image"></p>
<p><img src="https://pbs.twimg.com/media/Gf9p-rtbkAAseRf?format=jpg&name=4096x4096" alt="Image"></p>
<p><img src="https://pbs.twimg.com/media/Gf9qKauaAAARAUe?format=jpg&name=4096x4096" alt="Image"></p>
<p>笔者亲身验证，并且还因此在这里下载了一本书：《你好啊，区块链》。</p>
<p>ps：这本书在网上找不着资源，万能的 PDD 上要几百块钱呢。</p>
<h2>Vitesse Uni App</h2>
<p><a href="https://vitesse-docs.netlify.app/getting-started/introduction">介绍 | Vitesse uni-app</a>: vitess-uni-app 是一个基于 <code>Vue</code> 和 <code>uni-app</code> 的开源模板，采用直观且可扩展的方式创建类型安全、高性能和生产级的跨端应用。你可以直接开始编写 <code>.vue</code> 文件，而无需从头开始配置。</p>
<p>笔者最近接了一个小活，用 uniapp 创建安卓应用。当我用官网脚手架开发好功能之后，在打包安卓的时候却遇到各种问题，一些轻微的改动就会触发难以排查的失败。这让我非常沮丧，于是我搜索了相关的话题找到了这个脚手架，经过验证这个脚手架的功能非常完整，打包也没有任何问题。</p>
<p>最重要的是：<strong>vitesse-uni-app 充分拥抱开放生态，比如 VS Code 和 npm，希望能带给你更好的体验。</strong></p>
<h2>Wot Design Uni</h2>
<p>如果你想要有不错的 Uniapp 开发体验，那么可以考虑了解一下这个组件库：</p>
<p><a href="https://wot-design-uni.cn/">Wot Design Uni | 一个基于Vue3+TS开发的uni-app组件库，提供70+高质量组件，支持暗黑模式、国际化和自定义主题。</a></p>
<p>笔者使用下来，感觉这个库的颜值和交互还是不错的。</p>
<h2>React-trend</h2>
<p><a href="https://unsplash.github.io/react-trend/">react-trend 1.2.0 Demo</a> 展示了一个极简的 React 趋势图组件，用户可以配置颜色、宽度、形状和圆角尺寸来渲染一个简单的趋势图。</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/12/8adcc948997305e115d1c90d92f383f7.png" alt=""></p>
<p>源码地址如下：</p>
<p><a href="https://github.com/unsplash/react-trend">unsplash/react-trend: 📈 Simple, elegant spark lines</a>，这个组件已经稳定，不过维护人没有时间精力继续维护，并且建议用户自己通过源代码了解怎么如何搭建这个组件。</p>
<p>绘图工具通常很复杂、抽象、还沉甸甸的。它们需要专业知识，还得花不少时间。虽然你要为分析应用做丰富的数据可视化，这确实是必要的代价。但如果你只是需要一个能显示趋势的简单折线图呢？</p>
<p>毫无疑问，在时间和精力有限的情况下，这是一个极佳的趋势图组件选项。</p>
<h2>WebChat</h2>
<p><a href="https://github.com/molvqingtai/WebChat">molvqingtai/WebChat: 💬 Chat with anyone on any website.</a> 是一个基于 webrtc 技术的匿名聊天 Chrome 插件，其提供了对同一站点的端对端加密通信，主要利用的技术栈如下：</p>
<ul>
<li>remesh JavaScript 框架</li>
<li>Shadcn/ui 组件库</li>
<li>wxt Chrome 插件开发库</li>
<li>artico WebRTC 解决方案</li>
<li>Ugly-avatar 随机头像生成</li>
</ul>
<p>某种意义上来说，我们可以参考这个作品，把端对端的加密通信用到自己的作品中去。这是一种技术，现在你只是缺少创意。</p>
<h2>ugly-face</h2>
<p><a href="https://txstc55.github.io/ugly-avatar/">Ugly Avatar</a> 是一个开源的漫画风头像生成应用，作者表示：这个项目是在“署名-非商业性使用 4.0 国际许可协议”下。也就是说，这个项目不能用于商业用途。我考虑了一段时间。如果你想在需要头像生成器的网站上使用它，那你可以随便用。但是，如果你用这段代码单独开发一个应用作为你的主要项目，我非常反对这样做。我不想有“有人复制了我的代码然后赚钱”的烦恼。</p>
<p>🤣，代码短小精悍，非常清晰地介绍了如何生成一个随机的头像，仅阅读这个代码也是一个不错的学习 svg 知识的机会。</p>
<h2>Superjson</h2>
<p><a href="https://github.com/flightcontrolhq/superjson">flightcontrolhq/superjson: Safely serialize JavaScript expressions to a superset of JSON, which includes Dates, BigInts, and more.</a></p>
<p>如上所示，<code>superjson</code>是一个支持完备 JavaScript 对象类型 JSON 超集，以下是二者序列化和反序列化数据能力的对比表格：</p>
<table>
<thead>
<tr>
<th>type</th>
<th>supported by standard JSON?</th>
<th>supported by Superjson?</th>
</tr>
</thead>
<tbody><tr>
<td><code>string</code></td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td><code>number</code></td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td><code>boolean</code></td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td><code>null</code></td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td><code>Array</code></td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td><code>Object</code></td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td><code>undefined</code></td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><code>bigint</code></td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><code>Date</code></td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><code>RegExp</code></td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><code>Set</code></td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><code>Map</code></td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><code>Error</code></td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><code>URL</code></td>
<td>❌</td>
<td>✅</td>
</tr>
</tbody></table>
<p>如果你需要在某些场景下处理复杂 JavaScript 数据的序列化和反序列化的问题，这个库一定能帮助到你。</p>
<h2>archive web page</h2>
<p><a href="https://github.com/webrecorder/archiveweb.page">webrecorder/archiveweb.page: A High-Fidelity Web Archiving Extension for Chrome and Chromium based browsers!</a> 是一个备份整理 web 页面的浏览器插件，可以用来在浏览器里直接运行的高保真互动式网页存档。</p>
<h2>临时云盘</h2>
<p><a href="https://www.wenshushu.cn/">文叔叔 - 传文件，找文叔叔（大文件、永不限速）</a> 是一个不需要登录的临时文件传输服务，单文件最大 5G，非常适合临时使用，另外支持客户端和小程序。</p>
<h2>点对点临时传输</h2>
<p><a href="https://send-anywhere.com/#transfer">Send Anywhere - File transfer</a> 是一款非常知名的跨平台文件传输工具，2012 年推出至今已经运营 10 年时间。</p>
<p>可以通过密匙、二维码、链接、邮件分享和下载文件。<strong>而且使用密匙传输时直接在两台设备间传输文件，不会存储在服务器上</strong>，更安全可靠。</p>
<p>如果愿意接受实时地传输，那么看起来比上面的临时云盘更好用，直接跳过第三方服务点对点传输更快。</p>
<p>但是临时云盘依然有意义，文件可以短暂保存在服务器上，我们可以先去做别的事情。</p>
<h2>ZK101</h2>
<p><a href="https://www.zk101.io/">ZK101 - 零知识学习零知识</a> 是一个致力于让零知识证明（Zero-Knowledge Proofs）变得易于理解和学习的开源教育平台。</p>
<ul>
<li>📚 <strong>循序渐进</strong> - 从基础概念开始，逐步深入到高级主题</li>
<li>🎯 <strong>实践导向</strong> - 通过实际案例和代码示例加深理解</li>
<li>🔍 <strong>深入浅出</strong> - 复杂概念简单化，让零知识证明不再神秘</li>
<li>🌟 <strong>持续更新</strong> - 及时跟进零知识证明领域的最新发展</li>
</ul>
<h2>实时语音交互</h2>
<p><a href="https://github.com/realtime-ai/openai-realtime-webrtc-go">realtime-ai/openai-realtime-webrtc-go: openai webrtc go</a> ： 使用 Go 语言实现的 OpenAI Realtime API WebRTC 客户端，支持实时语音交互。</p>
<h2>笑</h2>
<p>独立开发者：</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/12/f28cf48b6c5c0e62ec9a24e2b809220b.png" alt=""></p>
<p>电影《误杀3》</p>
<p><img src="https://nfassetoss.southcn.com/__asset/30889f0a4f/93e83cda7f.jpg" alt="主演肖央"></p>
<p>周末和女朋友一起去看了电影《误杀3》，看的时候我就想到这种内容不可能在内地拍，因为很多场景根本没办法拍，那些行为很容易就会被控制，人们能做的事情只有相信大喇叭，亦或是用自己的脑子思考大喇叭所说的内容是否真实。</p>
<p>当你突破语言的限制，离开局域网把眼光放大到世界，使用另一种语言去获取信息和书籍，你才能够得到对事物更广阔的信息之源。</p>
<h2>言</h2>
<p>韩国空难 RIP</p>
<p><img src="https://pbs.twimg.com/media/Gf-nOoZa4AAUO5_?format=jpg&name=medium" alt="Image"></p>
<p><img src="https://pbs.twimg.com/media/Gf-nZULboAAfNDm?format=jpg&name=medium" alt="Image"></p>
<p><img src="https://pbs.twimg.com/media/Gf-nknMaQAAQSww?format=jpg&name=medium" alt="Image"></p>
<p><img src="https://pbs.twimg.com/media/Gf-nyaTaEAARX87?format=jpg&name=medium" alt="Image"></p>
<p>改变自己的第一步，不是洗心革面，来一次彻头彻尾的大改造，而是把以前想改变但半途而废的事做起来。比如把买了没读的书先看完，报了名没认真学的课去学一遍。</p>
<p><strong>长期软件开发文摘（源自阮一峰周刊，推荐去关注一下）：</strong></p>
<p>有些领域的软件会持续运行几十年，比如发电厂、起搏器、飞机、桥梁、重型机械的软件。它们可能几年都不会改动，然后推出一个新的大版本。</p>
<p>如果一个软件的开发周期长达几十年，需要长期维护，那么最好做到下面几点。</p>
<p>（1）尽量减少依赖。</p>
<p>软件的依赖项越多，长期越难以维护。依赖包括开发时依赖和运行时依赖，都是越少越好。</p>
<p>现在，很多软件在运行时会调用云服务，这也不利于长期维护。</p>
<p>（2）完备的测试用例。</p>
<p>测试对于重构、删除/添加功能，会提供极大的帮助。当你中断3年后，重新开始开发，测试也会让你快速了解系统。</p>
<p>（3）减少复杂性。</p>
<p>复杂性是软件开发的头号敌人，会让最好的程序员和团队都铩羽而归。</p>
<p>由于熵增定律和人类行为，除非你有意识地遏制，否则复杂性总是会增加。</p>
<p>因此，你需要养成严格的开发习惯：尽早和频繁地重构，删除不必要的或重复的代码，花时间简化。</p>
<p>（4）编写简单无趣的代码。</p>
<p>代码越简单越好，重点是代码的运行逻辑要显而易见。你永远不会后悔编写了简单的代码。</p>
<p>那些看上去很聪明、很高深的代码，会让后期的调试和理解变得复杂。特别注意那些高性能代码，只有当你正确理解它们时，它们才有效。</p>
<p>另外，那些眼下时髦、被热炒的明星技术，如果没有得到充分验证，也需要规避。</p>
<p>你最好只使用至少有10年历史的可靠技术。有一条规则是，某项技术的寿命与它们当前的年龄成正比，即存在越久的东西越可能继续存在。</p>
<p>（5）日志、遥测和文档</p>
<p>如果软件不是持续更新，开发者的注意力就会转到其他地方，不会立即跟进，所以需要有日志和遥测，能把运行过程记录下来。</p>
<p>文档则可以帮助我们理解几年前、甚至十几年前，编写原始代码时的想法。可能的话，记录所有事物，不仅仅是代码，还有理念、想法和为什么。</p>
<p>（6）团队</p>
<p>团队人员变化是很常见的。在许多地方，在一个团队呆三年，就已经很久了。虽然你可以用良好的文档和出色的测试，来抵消这种人员变化，但这很困难。</p>
<p>软件长寿的最简单办法之一，就是让开发成员长期稳定，保持工作十年。这意味着，你必须给你的程序员提供良好待遇，否则人们会离开。</p>
<p>在某些地方，软件是外包公司或咨询顾问写的，他们将代码丢到你的系统中后离开。对于长期运行的软件，这是非常糟糕的安排。</p>
<p>（7）开源</p>
<p>让你的代码暴露在外界的眼光，是保持代码可靠的好方法。一个有趣的事实是，只有质量良好的代码，人们才愿意对外分享，也就是说，如果不开源，人们会愿意在组织内部接受质量更差的代码。</p>
<p>开源代码有更高的标准、更多的测试，这是让代码不过时的绝佳机制。</p>
<h1>最后</h1>
<p>最近<del>开始准备</del>一直在找远程工作，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[周刊 No.65，虽迟但到]]></title>
            <link>https://i5lin.top/blog/post/2744404935</link>
            <guid isPermaLink="false">2744404935</guid>
            <pubDate>Tue, 17 Dec 2024 09:34:40 GMT</pubDate>
            <description><![CDATA[OK！继续来分享上~~一周~~（月）接触到的一些讯息！新生代打工人为什么需要双休，我们需要用来玩的休息日和用来休息的休息日]]></description>
            <content:encoded><![CDATA[<p>OK！继续来分享上<del>一周</del>（月）接触到的一些讯息！</p>
<p>“新生代打工人为什么需要双休，我们需要用来玩的休息日和用来休息的休息日”</p>
<h2>Hello Web Design</h2>
<p>《Hello Web Design》，一本十分短小的Web设计书，感觉特别适合像我这种非设计专业的一般读者，没有设计、排版、颜色理论的大道道，而是提供可以遵循的基本原则和捷径。</p>
<h2>艺爪付费</h2>
<p><a href="https://www.ezfuns.com/docs/revenue/">艺爪付费</a>为网站App提供会员、订阅、内购付费功能，其使用场景如下：</p>
<p>例如SaaS工具类产品，接入艺爪付费即可支持充值会员、续费、订单管理等功能。不需要开发UI界面，不需要开发订单系统，也不需要对接各个支付渠道。</p>
<p>常见的会员包括年度会员、月度会员、普通会员、高级会员、永久会员等。</p>
<blockquote>
<p>累计收款100万元内免费，超出后按1%收取服务费。企业用户可商议优惠费率及私有化部署。</p>
</blockquote>
<h2>Paddle</h2>
<p><a href="https://www.paddle.com/">Paddle - Payments, tax and subscription management for SaaS and digital products</a> 支持支付宝的订阅制收款服务。</p>
<h2>Remixicon</h2>
<p><a href="https://remixicon.com/">Remix Icon - Open source icon library</a>，开源的中性风格系统图标，是专门为设计师和开发者精心制作的。这些图标无论是个人使用还是商业用途都是免费的。</p>
<p>感谢友人刘推荐~</p>
<h2>现代 CSS 方案</h2>
<p><a href="https://moderncss.dev/">Modern CSS Solutions</a>：假如你对 CSS 感兴趣，或者写习惯了 Tailwind 这种格式化的样式，或许可以去看看这个现代 CSS 解决方案，介绍了很不错最近几年不错的 CSS 的写法，可以去把玩把玩。</p>
<h2>Logofa</h2>
<p>Logofa 是2023年Producthunt年度优秀设计辅助工具，尤其适合缺少专业设计师的产品 logo 设计！</p>
<h2>PiliPala - Bilibili 第三方安卓客户端</h2>
<p><a href="https://github.com/guozhigq/pilipala">PiliPala - 使用 Flutter 开发的 BiliBili 第三方客户端</a>，开发者收集官方 API 构建的极简客户端，去除了干扰性的内容，强烈推荐！</p>
<h2>卡卡字幕助手</h2>
<blockquote>
<p>前几天，人人影视开源了多年来保存的字幕数据库和一些内部资源。谈及开源的原因，其中一条就是 AI 生成字幕已经非常准确且快速，时代变了。</p>
</blockquote>
<p><a href="https://github.com/WEIFENG2333/VideoCaptioner">WEIFENG2333/VideoCaptioner: 🎬 卡卡字幕助手 | VideoCaptioner - 基于 LLM 的智能字幕助手，无需GPU一键高质量字幕视频合成！视频字幕生成、断句、校正、字幕翻译全流程。让字幕制作简单高效！</a></p>
<h2>智谱 GLM-4V-Flash 免费大模型 API</h2>
<p><a href="https://bigmodel.cn/">GLM-4-Flash 是<strong>智谱首次开放免费API 的大模型</strong>，支持长度达128K 的上下文。</a>，开发者可以轻量免费调用这个服务处理一些 AI 需求的逻辑，例如构建知识库、AI 翻译、内容总结等。</p>
<h2>Easydict 翻译</h2>
<blockquote>
<p>再次推荐 Easydict 是因为这个开源免费的 MacOS 翻译软件使用 Switft 进行重构之后，体积大大缩小，内存占用也降低了。并且作为 Bob 翻译的平替，还支持超多服务商接入，并且内置了智谱的 GLM-4V-Flash 大模型翻译，功能非常丰富</p>
</blockquote>
<p><a href="https://github.com/tisfeng/Easydict">Easydict</a> 是一个简洁易用的词典翻译 macOS App，能够轻松优雅地查找单词或翻译文本。Easydict 开箱即用，能自动识别输入文本语言，支持输入翻译，划词翻译和 OCR 截图翻译，可同时查询多个翻译服务结果，目前支持 <a href="https://www.youdao.com/">有道词典</a>，<a href="https://github.com/tisfeng/Easydict/blob/main/docs/How-to-use-macOS-system-dictionary-in-Easydict-zh.md"><strong>🍎 苹果系统词典</strong></a>，<a href="https://github.com/tisfeng/Easydict/blob/main/docs/How-to-use-macOS-system-translation-in-Easydict-zh.md">🍎 <strong>苹果系统翻译</strong></a>，<a href="https://chat.openai.com/">OpenAI</a>，<a href="https://gemini.google.com/">Gemini</a>，<a href="https://www.deepl.com/translator">DeepL</a>，<a href="https://translate.google.com/">Google</a>，<a href="https://fanyi.qq.com/">腾讯</a>，<a href="https://www.bing.com/translator">Bing</a>，<a href="https://fanyi.baidu.com/">百度</a>，<a href="https://niutrans.com/">小牛翻译</a>，<a href="https://fanyi.caiyunapp.com/">彩云小译</a>，<a href="https://translate.alibaba.com/">阿里翻译</a> 和 <a href="https://translate.volcengine.com/translate">火山翻译</a>。</p>
<h2>15 张图带你深入理解浮点数</h2>
<p><img src="https://polarisxu.studygolang.com/posts/basic/imgs/float-point-content.png" alt="img"></p>
<p>文章推荐：<a href="https://polarisxu.studygolang.com/posts/basic/diagram-float-point/">15 张图带你深入理解浮点数</a></p>
<h2>The Kajitsu Playlist</h2>
<p>坂本龙一生前很喜欢去一家日本料理餐厅吃饭，餐馆很棒，但是音乐很糟，于是他免费帮助这个餐厅定制了一个合适的歌单，叫做 <strong>The Kajitsu Playlist</strong>，完全没有用他自己的音乐，挺适合安静场合听听。</p>
<p>坐下来，好好欣赏：<a href="https://www.youtube.com/watch?v=u1hq9W-d_z8&list=PLyF_H5mySFA3TqDp9uQZ0TJrjjJ_b38Ty">https://www.youtube.com/watch?v=u1hq9W-d_z8&amp;list=PLyF_H5mySFA3TqDp9uQZ0TJrjjJ_b38Ty</a></p>
<h2>笑</h2>
<p>Tailwindcss 之梗：</p>
<p><img src="https://pbs.twimg.com/media/Ge8t6dqbwAUXXfV?format=jpg&name=large" alt="Image"></p>
<p>养猫者</p>
<p><img src="https://pbs.twimg.com/media/GdTc5zma4AA7hsE?format=jpg&name=900x900" alt="Image"></p>
<p><img src="https://pbs.twimg.com/media/GbITp4mXYAAK9Vo?format=jpg&name=900x900" alt="Image"></p>
<h2>言</h2>
<p>「先做个垃圾出来」这句话，可以作为独立开发的座右铭了🫡</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/12/54d9ef1dd12a7c4f2a6299cdbfc05c7a.png" alt=""></p>
<h1>最后</h1>
<p>最近<del>开始准备</del>一直在找远程工作，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[妙才周刊 No.64]]></title>
            <link>https://i5lin.top/blog/post/2666816466</link>
            <guid isPermaLink="false">2666816466</guid>
            <pubDate>Mon, 18 Nov 2024 01:21:39 GMT</pubDate>
            <description><![CDATA[从 16 岁少年高中使用 AI 开发图片应用到 React Props 备忘清单图]]></description>
            <content:encoded><![CDATA[<p><strong>OK！继续来分享上一周（最近一个月）接触到的一些讯息！</strong></p>
<h2>Free Dev</h2>
<p><a href="https://free-for.dev/#/">Free for Developers</a> 开发者的免费服务列表，涵盖了软件开发的各方面功能，你可以在这里找到很多免费或者具有不错免费额度的服务。</p>
<h2>GPT Search</h2>
<p><strong>现在可以在 chrome 浏览器上添加新的站点搜索条目，修改默认的地址栏搜索引擎为 GPT Search 了！</strong></p>
<p><strong>只需要在设置里新增一个站点搜索，url 为：****<a href="https://chatgpt.com/?hints=search&q=%25s">https://chatgpt.com/?hints=search&amp;q=%s</a></strong> 即可！</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/10/c3a06ee0d5bc4caf043ca96af228463d.png" alt=""></p>
<p><strong>后续就可以用来作为默认搜索引擎，或者在地址栏中输入</strong> <code>@GPT</code>和空格设置临时搜索引擎进行搜索，确认后会跳转到 <code>ChatGPT</code>的搜索机制页面，实时对你的问题进行解析和搜索再回复。</p>
<h2>AI 边栏</h2>
<p><img src="https://de4965e.webp.li/blog-images/2024/10/8ba9668816846512cffbc216a5a3cf6f.png" alt=""> <a href="https://ziziyi.com/zh-cn/anything-copilot">Anything Copilot</a> 是一款免费的 chrome 插件，可以很方便的在侧边栏单独打开一个常驻的页面，玩法很多。</p>
<h2>全屏长图截取</h2>
<p><a href="https://chromewebstore.google.com/detail/fullpage-blitz-lightning/nnhplomobinijgpongnbofokpibpbioa?hl=en-US">Fullpage Blitz - Lightning-Fast Full Page Screenshots</a> 是一款支持全屏截图的 chrome 插件，速度非常快！</p>
<h2>使用 JavaScript 对图像进行量化并且提取颜色</h2>
<p><strong>在某些用户体验极佳的站点上常常看到某些高清图片在加载完成之前会提前渲染一个具有近似颜色的模糊背景，由此我想到如何去提取图像的主色这个问题，于是搜索到了这篇文章。</strong></p>
<p><strong>文章地址：</strong><a href="https://guqing.io/archives/k-means-main-color">https://guqing.io/archives/k-means-main-color</a></p>
<blockquote>
<p><strong>此外，顺藤摸瓜还找到了作者另一篇关于重构 Remember Me 机制的思考的文章：</strong><a href="https://guqing.io/archives/persistent-token-based-remember-me">Halo 2.17 为什么重构 RememberMe 机制的实现方式</a></p>
</blockquote>
<h2>VideoLingo</h2>
<p><a href="https://github.com/Huanshere/VideoLingo">Huanshere/VideoLingo</a>：接入不同的 AI 服务，直接对外语视频进行字幕翻译和覆盖，一键全自动搬运视频！</p>
<h2>Text-behind-image</h2>
<p><img src="https://textbehindimage.rexanwong.xyz/_next/static/media/pov.f0636119.png" alt=""></p>
<p><a href="https://textbehindimage.rexanwong.xyz/">Text Behind Image</a> 是香港一个 16 岁的高中生开发者借助 Cursor + V0 开发的图片+文字的图片设计应用，可以很轻松的把文字写入到图片内，并且自动分离图层，让文字隐藏在图片的核心内容之后。</p>
<p><img src="https://textbehindimage.rexanwong.xyz/_next/static/media/bear.bd699506.png" alt=""></p>
<p><img src="https://textbehindimage.rexanwong.xyz/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsnap.b2ea3ca4.png&w=828&q=75" alt=""></p>
<h2>带动画的 Icon</h2>
<p><img src="https://de4965e.webp.li/blog-images/2024/11/081b73d6fdaed37c00c7d074a8e6e7cd.png" alt=""></p>
<p><a href="https://icons.pqoqubbw.dev/">pqoqubbw/icons</a> 是一个带动画的开源的 svg 图标资源站点，用户可以免费使用。目前作者上传了常用场景下 112 个 icon，在项目中使用带动画的图标有时候会提高用户的体验，毕竟谁不喜欢有趣的事物呢。</p>
<h2>Copi 安全的 Macos 剪贴软件</h2>
<p><a href="https://github.com/s1ntoneli/Copi">s1ntoneli/Copi: A Secure Alternative to macOS Clipboard</a> 是一款开源、轻量且安全的 macOS 系统剪贴板替代工具。它拥有与系统剪贴板相同的复制粘贴能力，但其内容不会被任何第三方应用程序访问，有效保护您的隐私数据。</p>
<p><strong>如果你是数字货币拥有者，亦或是经常复制敏感信息且不希望系统剪贴板留下敏感信息的痕迹，那么可以试试这个应用。</strong></p>
<p><code>Copi</code>使用系统的自定义“服务”功能来获取和粘贴文字，而不再通过系统剪贴板。因此使用传统方法对“系统剪贴板”进行的各类攻击行为会全部失效。</p>
<p><img src="https://github.com/s1ntoneli/Copi/blob/main/Stuff/safecopy.png?raw=true" alt=""></p>
<h2>CSS z-index 查看</h2>
<p><a href="https://github.com/andreadev-it/stacking-contexts-inspector">andreadev-it/stacking-contexts-inspector: Documentation and source for the CSS Stacking Contexts Inspector devtools extension</a> 是一个 chrome 浏览器扩展，在元素 <code>zIndex</code>属性不符合预期的时候，可以用这个插件查看 CSS 内容栈详情，检查 <code>zIndex</code>的层级。</p>
<h2>数字动画组件</h2>
<p><a href="https://number-flow.barvian.me/">NumberFlow for React - Transition and format numbers</a> 是一个用于转换、格式化和本地化数字的组件，具有超强的动画渲染效果，让人眼前一亮。</p>
<blockquote>
<p><strong>支持 React/Vue/Svelte 无痛引入</strong></p>
</blockquote>
<p><strong>任何需要强化数字提示的地方，都可以考虑一下这个组件。</strong></p>
<h2>React 初学者备忘清单之 Props</h2>
<p><img src="https://pbs.twimg.com/media/Gbrhe9LagAAyYD4?format=jpg&name=large" alt="Image"></p>
<h1>最后</h1>
<p><strong>最近</strong><del>开始准备</del>一直在找工作，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p><strong>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</strong></p>
</blockquote>
<p><strong>好了，今天的分享就先到这里，大家下周见~</strong></p>
<p><strong>AND</strong></p>
<p><strong>最近遇到了人生中最艰难的一些事情，周刊也因此断更，即使是现在我依然还被困在其中。</strong></p>
<p><strong>生活依然继续，有的人爬到了山顶确实很了不起，我也只能从地底先破土而出再一步步向上攀爬。</strong></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Next.js15 + i18n 初体验]]></title>
            <link>https://i5lin.top/blog/post/2612130980</link>
            <guid isPermaLink="false">2612130980</guid>
            <pubDate>Thu, 24 Oct 2024 17:02:02 GMT</pubDate>
            <description><![CDATA[我需要为我的 nextjs 项目添加 i18n 功能，从而更好地进行推广和服务。于是我尝试使用next-intl库来完成这个功能，本文记录了我接入这个库的整个过程。]]></description>
            <content:encoded><![CDATA[<p>我需要为我的 nextjs 项目添加 i18n 功能，从而更好地进行推广和服务。于是我尝试使用 <code>next-intl</code>库来完成这个功能，本文记录了我接入这个库的整个过程。</p>
<h2>版本信息</h2>
<ul>
<li>nextjs 15<ul>
<li>app 路由</li>
<li>子域名方案</li>
</ul>
</li>
<li>next-intl 3.23.4</li>
</ul>
<h2>起步</h2>
<p>首先安装依赖 <code>next-intl</code>，在根目录下创建对应的目录：</p>
<pre><code class="language-bash">├── messages
│   ├── en.json (1)
│   └── ...
├── next.config.mjs (2)
└── src
    ├── i18n
    │   ├── routing.ts (3)
    │   └── request.ts (5)
    ├── middleware.ts (4)
    └── app
        └── [locale]
            ├── layout.tsx (6)
            └── page.tsx (7)
</code></pre>
<p>其次，创建翻译文件 <code>messages/en.json</code>和其他语言的翻译文件:</p>
<pre><code class="language-json">// en.json
{
  &quot;HomePage&quot;: {
    &quot;title&quot;: &quot;Aaron&#39;s Chrome Extension Store&quot;,
    &quot;description&quot;: &quot;A collection of Chrome extensions created by Aaron.&quot;,
    &quot;hello&quot;: &quot;Hello, world!&quot;
  },
  &quot;Metadata&quot;: {
    &quot;title&quot;: &quot;Aaron&#39;s Chrome Extension Store&quot;,
    &quot;description&quot;: &quot;A curated store of excellent extensions maintained and independently developed by Aaron.&quot;,
    &quot;keywords&quot;: &quot;Chrome extensions, Aaron, extension store&quot;
  }
}

// zh.json
{
  &quot;HomePage&quot;: {
    &quot;title&quot;: &quot;亚伦的 Chrome 扩展商店&quot;,
    &quot;description&quot;: &quot;亚伦创建的 Chrome 扩展集合。&quot;,
    &quot;hello&quot;: &quot;你好，世界！&quot;
  },
  &quot;Metadata&quot;: {
    &quot;title&quot;: &quot;亚伦的 Chrome 扩展商店&quot;,
    &quot;description&quot;: &quot;亚伦精心维护和独立开发的优秀扩展程序商店。&quot;,
    &quot;keywords&quot;: &quot;Chrome 扩展, 亚伦, 扩展商店&quot;
  },
  &quot;Locales&quot;: {
    &quot;en&quot;: &quot;英语&quot;,
    &quot;es&quot;: &quot;西班牙语&quot;,
    &quot;fr&quot;: &quot;法语&quot;,
    &quot;zh&quot;: &quot;中文&quot;
  },
  &quot;Common&quot;: {
    &quot;language&quot;: &quot;语言&quot;
  }
}
</code></pre>
<p>再次，配置 <code>next.config.ts</code>文件:</p>
<pre><code class="language-typescript">import createNextIntlPlugin from &#39;next-intl/plugin&#39;;
 
const withNextIntl = createNextIntlPlugin();
 
/** @type {import(&#39;next&#39;).NextConfig} */
const nextConfig = {};
 
export default withNextIntl(nextConfig);
</code></pre>
<p>然后，配置 <code>i18n</code>的路由文件 <code>src/i18n/routing.ts</code>：</p>
<pre><code class="language-typescript">import {defineRouting} from &#39;next-intl/routing&#39;;
import {createNavigation} from &#39;next-intl/navigation&#39;;
 
export const routing = defineRouting({
  // A list of all locales that are supported
  locales: [&#39;en&#39;, &#39;zh&#39;],
 
  // Used when no locale matches
  defaultLocale: &#39;en&#39;
});
 
// Lightweight wrappers around Next.js&#39; navigation APIs
// that will consider the routing configuration
export const {Link, redirect, usePathname, useRouter} =
  createNavigation(routing);
</code></pre>
<p>再然后，创建中间件 <code>src/middleware.ts</code>：</p>
<pre><code class="language-typescript">import createMiddleware from &#39;next-intl/middleware&#39;;
import {routing} from &#39;./i18n/routing&#39;;
 
export default createMiddleware(routing);
 
export const config = {
  // Match only internationalized pathnames
  matcher: [&#39;/&#39;, &#39;/(zh|en)/:path*&#39;]
};
</code></pre>
<p>由 <code>next-intl</code>提供的创建中间件的函数配合多语言的路由，即可创建一个中间件。与此同时，导出的 <code>config</code>对象限定了这个中间件的匹配范围。</p>
<p>然后，我们还需要创建一个 <code>src/i18n/request.ts</code>文件来提供一个函数来获取语言和对应语言的翻译文件。</p>
<pre><code class="language-typescript">import {getRequestConfig} from &#39;next-intl/server&#39;;
import {routing} from &#39;./routing&#39;;
 
export default getRequestConfig(async ({requestLocale}) =&gt; {
  // This typically corresponds to the `[locale]` segment
  let locale = await requestLocale;
 
  // Ensure that a valid locale is used
  if (!locale || !routing.locales.includes(locale as any)) {
    locale = routing.defaultLocale;
  }
 
  return {
    locale,
    messages: (await import(`../../messages/${locale}.json`)).default
  };
});
</code></pre>
<p>接下来，创建 <code>src/app/[locale]/layout.tsx</code>布局组件：</p>
<pre><code class="language-typescript">import { routing } from &#39;@/i18n/routing&#39;;
import { NextIntlClientProvider } from &#39;next-intl&#39;;
import { getMessages } from &#39;next-intl/server&#39;;
import localFont from &quot;next/font/local&quot;;
import { notFound } from &#39;next/navigation&#39;;
import &quot;../globals.css&quot;;
import SiteHeader from &#39;../view/SiteHeader&#39;;

const geistSans = localFont({
  src: &quot;../fonts/GeistVF.woff&quot;,
  variable: &quot;--font-geist-sans&quot;,
  weight: &quot;100 900&quot;,
});
const geistMono = localFont({
  src: &quot;../fonts/GeistMonoVF.woff&quot;,
  variable: &quot;--font-geist-mono&quot;,
  weight: &quot;100 900&quot;,
});

export default async function LocaleLayout({
  children,
  params
}: {
  children: React.ReactNode;
  params: Promise&lt;{ locale: string }&gt;;
}) {
  const { locale } = await params;
  // Ensure that the incoming `locale` is valid
  if (!routing.locales.includes(locale as any)) {
    notFound();
  }

  // Providing all messages to the client
  // side is the easiest way to get started
  const messages = await getMessages();
  return (
    &lt;html lang={locale}&gt;
      &lt;body className={`${geistSans.variable} ${geistMono.variable} antialiased`}&gt;
        &lt;NextIntlClientProvider messages={messages}&gt;
          &lt;SiteHeader /&gt;
          {children}
        &lt;/NextIntlClientProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<p>这里我们提供了一个 <code>NextIntlClientProvider</code>组件，将翻译信息传递了下去。</p>
<p>其中 <code>&lt;SiteHeader&gt;</code>单独拆成一个组件，是因为 <code>useTranslations</code>钩子必须在非异步组件内使用。</p>
<p>这里我将默认的 <code>src/app/layout.tsx</code>删掉了，然后在 <code>src/app/page.tsx</code>中做了重定向：</p>
<pre><code class="language-typescript">import { redirect } from &#39;next/navigation&#39;;

// This page only renders when the app is built statically (output: &#39;export&#39;)
export default function RootPage() {
  redirect(&#39;/en&#39;);
}
</code></pre>
<p>访问根目录时自动重定向到 <code>/en</code>路由下，并且经过中间件的处理。</p>
<blockquote>
<p>进入到不同的路由下会使得当前的语言被写入到客户端 <code>cookie</code>中的 <code>NEXT_LOCALE</code>字段，下次请求时带上此字段，<code>next-intl</code>就可以确定语言了。</p>
</blockquote>
<h2>编写页面</h2>
<p>现在来编写 <code>src/app/[locale]/page.tsx</code>：</p>
<pre><code class="language-typescript">import { useTranslations } from &#39;next-intl&#39;;

export default function HomePage() {
  const t = useTranslations(&#39;homepage&#39;);
  return (
    &lt;div className=&#39;flex flex-col items-center m-12 gap-4&#39;&gt;
      &lt;h1&gt;{t(&#39;hello&#39;)}&lt;/h1&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>此时，我们进入 <code>/en</code>和 <code>/zh</code>两个路由下的页面如下：</p>
<p>中文：</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/10/700127640fe787b019b5b35767795fcc.png" alt=""></p>
<p>英文：</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/10/ef1fb872e95195975eea37c88b1c1490.png" alt=""></p>
<h2>静态渲染</h2>
<p>目前版本的 <code>next-intl</code>仅在服务器组件中使用 <code>useTranslations</code>来实现多语言，未来将会消除这个限制，让客户端组件也能自由切换多语言。</p>
<p>在静态渲染时，<code>next-intl</code>提供了一个临时的 api 来启用多语言功能。</p>
<p>由于我们使用了 <code>[locale]</code>动态路由，因此我们需要通过 <code>generateStaticParams</code>函数，将所有可能的语言路由传给 <code>nextjs</code>，以便于在构建时渲染。</p>
<p>在 <code>app/[locale]/layout.tsx</code>组件中添加代码：</p>
<pre><code class="language-typescript">export function generateStaticParams() {
  return routing.locales.map((locale) =&gt; ({locale}));
}
</code></pre>
<p>显然 <code>routing</code>来源于我们的 <code>i18n/routing</code>，声明了我们支持的哪些语言。</p>
<p>此外，我们还需要在布局组件中显式地声明启用静态渲染多语言：</p>
<pre><code class="language-typescript">...
...

export default async function LocaleLayout({
  children,
  params
}: {
  children: React.ReactNode;
  params: Promise&lt;{ locale: string }&gt;;
}) {
  const { locale } = await params;
  ...
  // Enable static rendering
  setRequestLocale(locale);
  ...
}
</code></pre>
<blockquote>
<p>我们需要在要启用静态渲染的每个页面和每个布局中调用此函数，因为 Next.js 可以独立渲染布局和页面。</p>
</blockquote>
<p><code>setRequestLocale</code>函数让 <code>nextjs</code>知悉当前页面的语言。</p>
<p>这听起来有点模糊，目前 <code>nextjs</code>并没有提供任何 API 来从路由参数中读取类似 locale 这样的内容。而语言是 <code>next-intl</code>的关键信息，这部分功能只能通过 <code>next-intl</code>来增强。</p>
<p>关键点：</p>
<ol>
<li>通常在根布局（root layout）调用一次就足够了</li>
<li>如果页面需要在生成静态参数或元数据时使用翻译，那么该页面也需要调用</li>
<li>普通组件（包括服务端组件和客户端组件）不需要调用</li>
<li>这个函数的调用应该总是在使用任何翻译功能之前</li>
</ol>
<p>举个例子：</p>
<pre><code class="language-bash">app/
├── [locale]/           # 语言路由必须在这一层
│   ├── layout.tsx     # ✅ 在这里调用 setRequestLocale
│   ├── page.tsx
│   └── about/
│       └── page.tsx   # ❌ 不需要重复调用，已被 [locale]/layout.tsx 覆盖
│
└── layout.tsx         # ❌ 在这里调用无效，因为在语言路由之外
</code></pre>
<h2>在 metadata 中使用多语言</h2>
<p>在完成了上述的配置后，我们可以在 <code>generateMetadata</code>函数的参数中得到 <code>locale</code>，在此函数内部使用 <code>getTranslations</code>函数获取翻译功能的 <code>t</code>函数。</p>
<pre><code class="language-typescript">export async function generateMetadata({ params }: {
  params: Promise&lt;{
    locale: string
  }&gt;
}) {
  const { locale } = await params
  const t = await getTranslations({ locale, namespace: &#39;Metadata&#39; });

  return {
    title: t(&#39;title&#39;),
    description: t(&#39;description&#39;),
    keywords: t(&#39;keywords&#39;),
  };
}
</code></pre>
<p>如此一来便可以在不同语言录音下的页面添加多语言的 metadata 信息了。</p>
<h2>启用 TypeScript 支持</h2>
<p><code>next-intl</code>仅需要我们在根目录创建一个<code>global.d.ts</code>文件，声明类型即可：</p>
<pre><code class="language-typescript">import en from &quot;./messages/en.json&quot;;

type Messages = typeof en;

declare global {
  // Use type safe message keys with `next-intl`
  type IntlMessages = Messages;
}
</code></pre>
<p>后续使用<code>t()</code>的时候编辑器就能提供候选补全了。</p>
<h2>编写切换语言功能</h2>
<p>首先，我们创建一个 <code>SiteHeader</code>组件：</p>
<pre><code class="language-typescript">import { useLocale, useTranslations } from &quot;next-intl&quot;;
import Link from &quot;next/link&quot;;
import { PiGoogleChromeLogoDuotone } from &quot;react-icons/pi&quot;;
import { routing } from &#39;../../../i18n/routing&#39;;
import LanguageSwitch from &quot;./LanguageSwitch&quot;;

export default function SiteHeader() {
  const t = useTranslations(&#39;HomePage&#39;);
  const locale = useLocale();
  return (
    &lt;div className=&quot;mx-auto&quot;&gt;
      &lt;div className=&#39;flex items-center gap-4 2xl:w-[1360px] p-4&#39;&gt;
        {/* add website header here */}
        &lt;Link href={&#39;/&#39;} locale={locale} className=&quot;flex items-center gap-1&quot;&gt;
          &lt;PiGoogleChromeLogoDuotone className=&#39; text-xl 2xl:text-3xl text-blue-600&#39; /&gt;
          &lt;h1 className=&quot;mr-auto&quot;&gt;{t(&#39;title&#39;)}&lt;/h1&gt;
        &lt;/Link&gt;
        &lt;div className=&quot;mr-auto&quot;&gt;
          {/* add other nav */}
        &lt;/div&gt;
        {/* select to switch language */}
        &lt;LanguageSwitch locales={routing.locales as unknown as string[]} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<p>以及一个核心切换的客户端组件：</p>
<pre><code class="language-typescript">&quot;use client&quot;

import clsx from &quot;clsx&quot;;
import { useLocale, useTranslations } from &quot;next-intl&quot;;
import Link from &quot;next/link&quot;;
import { usePathname } from &quot;next/navigation&quot;;
import { LiaLanguageSolid } from &quot;react-icons/lia&quot;;

export default function LanguageSwitch({ locales }: { locales: string[] }) {
  const pathname = usePathname()
  const locale = useLocale()

  const t = useTranslations()

  return (
    &lt;div className=&quot;relative group&quot;&gt;
      &lt;div className=&quot;flex items-center gap-1 bg-blue-500 rounded-full px-2 p-0.5 text-white&quot;&gt;
        &lt;LiaLanguageSolid /&gt; {t(&#39;Common.language&#39;)}
      &lt;/div&gt;
      &lt;div className=&quot;absolute top-[100%] flex-col bg-gray-50/80 inset-x-0 rounded-md p-1 gap-1 border hidden group-hover:flex&quot;&gt;
        {locales.map((lang) =&gt; (
          &lt;Link className={clsx(lang === locale ? &#39;text-blue-600 bg-white&#39; : &#39;text-gray-600&#39;, &#39;text-sm px-1 hover:bg-white&#39;)} key={lang} href={pathname.replace(/^\/[^/]+/, `/${lang}`)} locale={lang}&gt;
            {t(`Locales.${lang}`)}
          &lt;/Link&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;

  )
}
</code></pre>
<p>最后的效果如下，在鼠标移动到上面时通过 <code>tailwindcss</code>控制切换标签的显示和隐藏，语言数组从配置文件中导出，再使用 <code>Link</code>组件配合 <code>locale</code>属性就能在点击的时候切换到不同的语言路由下了：</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/10/348ff386b1cc6bdeb0ccc0c3f9c8a208.png" alt=""></p>
<p><img src="https://de4965e.webp.li/blog-images/2024/10/30f6ec8bce01cd021355b61bf855ce79.png" alt=""></p>
<h2>最后</h2>
<p>好了，今天分享就到这里。</p>
<p>我预计需要一些时间完成这个网站，我打算把自己开发的所有浏览器插件全部放到这上面，再加上用户体系控制，分享一些我开发的免费的简单插件和部分预计可以收费的核心插件。</p>
<p>为了记录这个过程，我会把遇到的问题和解决方案都发布在公众号上，欢迎阅读和分享。</p>
<p>再会。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[State of HTML 2024 调查问卷分享]]></title>
            <link>https://i5lin.top/blog/post/2595074461</link>
            <guid isPermaLink="false">2595074461</guid>
            <pubDate>Thu, 17 Oct 2024 15:29:22 GMT</pubDate>
            <description><![CDATA[前几天我收到了 《State of HTML 2024》的调查问卷邮件，于是倒了杯水就打算把问卷填了，还在里面看到了一些没接触过的新知识，今天来分享一下整个问卷的一些内容，如果你没有收到这篇问卷，欢迎倒杯水，看看都有些什么。]]></description>
            <content:encoded><![CDATA[<p>前几天我收到了 <code>State of HTML 2024</code>的调查问卷邮件，于是倒了杯水就打算把问卷填了，还在里面看到了一些没接触过的新知识，今天来分享一下整个问卷的一些内容，如果你没有收到这篇问卷，欢迎倒杯水，看看都有些什么。</p>
<blockquote>
<p>传送门：<a href="https://survey.devographics.com/en-US/survey/state-of-html/2024">https://survey.devographics.com/en-US/survey/state-of-html/2024</a></p>
</blockquote>
<p>本篇问卷主要有以下内容：</p>
<ul>
<li>表单</li>
<li>交互性</li>
<li>内容</li>
<li>Web 组件</li>
<li>无障碍</li>
<li>类原生的 Web 应用</li>
<li>其他工具和功能</li>
</ul>
<p>每一个问题都有三个选项：</p>
<ul>
<li>🤷 没听说过</li>
<li>👀 有听说过</li>
<li>🤓 用过</li>
</ul>
<p>以及是否感兴趣和体验如何的额外选项。</p>
<blockquote>
<p>推荐自己填写一遍，后续还可以收到年度邮件，甚至可以在填写的时候选择感兴趣的内容，后续会收到更新的信息。</p>
</blockquote>
<h2>表单</h2>
<h3>datalist</h3>
<blockquote>
<p>没听说过，很感兴趣！</p>
</blockquote>
<p>支持一个可选列表作为 <code>input</code>输入的自动填充选项的功能，看代码：</p>
<pre><code class="language-html">&lt;form action=&quot;/action_page.php&quot; method=&quot;get&quot;&gt;
  &lt;label for=&quot;browser&quot;&gt;Choose your browser from the list:&lt;/label&gt;
  &lt;input list=&quot;browsers&quot; name=&quot;browser&quot; id=&quot;browser&quot;&gt;
  &lt;datalist id=&quot;browsers&quot;&gt;
    &lt;option value=&quot;Edge&quot;&gt;
    &lt;option value=&quot;Firefox&quot;&gt;
    &lt;option value=&quot;Chrome&quot;&gt;
    &lt;option value=&quot;Opera&quot;&gt;
    &lt;option value=&quot;Safari&quot;&gt;
  &lt;/datalist&gt;
  &lt;input type=&quot;submit&quot;&gt;
&lt;/form&gt;
</code></pre>
<p>这个标签笔者毫无印象，但是看起来非常适合做表单中的字符串输入功能，尤其是需要提供一定的动态或静态的选项时使用。</p>
<p>需要注意的是，这个标签的功能和 <code>Select</code> 标签没有太多重合，<code>option</code>提供支持嵌套文本，但是渲染需要自定义控制才好看，浏览器默认的渲染比较丑，并且如果选择了 <code>option</code>，那么 <code>input</code>的值也会渲染成 <code>option</code>的 <code>value</code>，那种后端枚举选项作为表单值的情况就不太适合了。</p>
<h3>autocomplete</h3>
<blockquote>
<p>用过，想再次使用</p>
</blockquote>
<p>大多数浏览器会根据输入框的 <code>name</code>去保存一些信息（通常浏览器会弹窗一次询问用户是否保存数据），如果开发者给输入框添加了 <code>autocomplete</code>属性，浏览器就会弹出对应保存的 <code>name</code>的数据作为自动填充的内容。</p>
<p>看代码：</p>
<pre><code class="language-html">&lt;input name=&quot;address_firstline&quot; autocomplete=&quot;billing street-address&quot; /&gt;
</code></pre>
<h3>HTML 媒体捕获</h3>
<blockquote>
<p>没听过，感兴趣</p>
</blockquote>
<p>从用户的相机捕获文件（仅支持移动端浏览器）：</p>
<pre><code class="language-html">&lt;input type=&quot;file&quot; accept=&quot;video/*&quot; capture&gt;
</code></pre>
<p>笔者极少做移动端的功能，对此毫无印象，不过看起来还是很实用的。</p>
<h3>input.showPicker()</h3>
<blockquote>
<p>没听过，感兴趣</p>
</blockquote>
<p>程序从控制端打开一个选择器（选择颜色、日期等）</p>
<pre><code class="language-html">&lt;input id=&quot;dateInput&quot; type=&quot;date&quot;&gt;
&lt;button onclick=&quot;dateInput.showPicker()&quot;&gt;Select date&lt;/button&gt;
</code></pre>
<p>这是 <code>input</code>元素的一个方法，开发者可以获取一个 <code>input</code>去调用此方法显示选择器，但是看了看兼容性：</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/10/beae3f0c884251ac26c200a4c5b2c0a8.png" alt=""></p>
<p>如图所示，基本都是最近 2 年才支持的功能 ，如果你的产品用户使用一些旧的浏览器，可能需要额外的 polyfill 来支持此功能。</p>
<h3>FormData API</h3>
<blockquote>
<p>没听说过，没啥兴趣</p>
</blockquote>
<p>这是一个允许通过 JS 更轻松地提取和操作表单数据值的 API。</p>
<pre><code class="language-js">let fd = new FormData(form);
let data = JSON.stringify(Object.fromEntries(fd));
</code></pre>
<p>自动从提供的 <code>form</code>标签提取所有表单字段，创建一个对象，可以再通过 <code>JSON</code>序列换转为一个对象。笔者开发表单几乎不会使用原生的 <code>name</code>和 <code>value</code>这样的值来控制数据，极少有使用原生 <code>form</code>的功能来直接创建表单和提交数据的场景和需求。</p>
<p>此前不知，现在稍微了解一点也不错。</p>
<h3>Customizable Select</h3>
<blockquote>
<p>听过，感兴趣</p>
</blockquote>
<p>自定义伪类和属性来控制 <code>select</code>的样式：</p>
<pre><code class="language-css">select,
::picker(select) {
  appearance: base-select;
}
</code></pre>
<p>默认情况下，浏览器会为 <code>&lt;select&gt;</code> 元素提供特定的外观样式，比如下拉箭头、边框等。<code>::picker</code> 允许开发者定制表单选择器的下拉箭头或其他类似的 UI 元素（例如日期选择器或颜色选择器），现如今组件开发大行其道，在不需要自己控制样式时，稍微了解一下即可。</p>
<h3>contenteditable 和 plaintext-only</h3>
<blockquote>
<p>听过，感兴趣</p>
</blockquote>
<p>标签可以用 <code>contenteditable</code>属性来允许用户直接编辑内部文本，结合 <code>plaintext-only</code>则限定了输入格式为纯文本。</p>
<pre><code class="language-html">&lt;h2 class=&quot;title&quot; contenteditable=&quot;plaintext-only&quot;&gt;&lt;/h2&gt;
</code></pre>
<p>默认此属性的值为 <code>false</code>，即不允许编辑。但是，也可以设置为 <code>true</code>允许用户编辑，此时用户不仅可以编辑文本，也可以插入 <code>html</code>元素，因此某些场景下需要限制为纯文本。</p>
<h3>EditContext</h3>
<blockquote>
<p>没听过，感兴趣</p>
</blockquote>
<p>这是一个 2024 年才提出的新接口，<code>EditContext</code> 的主要目标是为富文本编辑器、代码编辑器、虚拟键盘应用程序等提供更好的输入控制和交互方式，尤其在复杂的输入场景中，能更灵活地处理光标位置、文本选区、输入模式等。</p>
<pre><code class="language-js">const canvas = document.createElement(&quot;canvas&quot;);
const editContext = new EditContext();
canvas.editContext = editContext;
</code></pre>
<p>目前依然是一个实验性功能，兼容性堪忧。不开发富文本的内容，几乎用不上。而一旦开发富文本，恭喜你进入了一个天坑。</p>
<h3>caretPositionFromPoint</h3>
<blockquote>
<p>没听说过，不感兴趣</p>
</blockquote>
<p><code>caretPositionFromPoint</code> 是一个实验性的 Web API，主要用于获取光标（插入符号）在页面上的精确位置。它允许开发者根据用户在页面上点击的位置（通过屏幕坐标 <code>x</code> 和 <code>y</code>）来确定光标应该插入文本的具体位置。</p>
<p>这个 API 在处理文本编辑、富文本编辑器以及复杂的鼠标操作（例如点击某个位置并移动光标）时非常有用。</p>
<p>告辞！</p>
<h2>交互性</h2>
<h3>dialog</h3>
<blockquote>
<p>听说过，感兴趣</p>
</blockquote>
<p>通过对话框交互是一个常见的需求，原生的 <code>dialog</code>目前已经是一个广泛支持的功能了（类似火狐、Safira已经在两年前支持此标签）。</p>
<pre><code class="language-html">&lt;dialog id=&quot;confirm&quot;&gt;
	&lt;form method=&quot;dialog&quot;&gt;
		Are you sure?
		&lt;button value=&quot;1&quot;&gt;Yes&lt;/button&gt;
		&lt;button value=&quot;0&quot;&gt;No&lt;/button&gt;
	&lt;/form&gt;
&lt;/dialog&gt;
</code></pre>
<p>这个标签支持模态和非模态，也就是说开发者可以控制打开此对话框时，用户是否可以和页面其他标签交互。</p>
<p>总的来说，<code>html</code>在进步~</p>
<h3>details 和 summary</h3>
<blockquote>
<p>用过，想再次使用</p>
</blockquote>
<p>HTML 原生可以交互切换以隐藏或显示内容的公开小部件，用在一些需要控制显示和隐藏的边栏内容时很方便，我们不需要创建一些状态值去保存和控制其状态。</p>
<pre><code class="language-html">&lt;details&gt;
    &lt;summary&gt;Details&lt;/summary&gt;
    Longer content
&lt;/details&gt;
</code></pre>
<h3>弹出窗口（popover） API</h3>
<blockquote>
<p>没听过，感兴趣</p>
</blockquote>
<p>2023 年左右发布的新特性，提供一种方式允许开发者将弹出窗口和元素之间绑定，改变一直以来的弹出窗口实现方案。</p>
<pre><code class="language-html">&lt;button popovertarget=&quot;foo&quot;&gt;Toggle the popover&lt;/button&gt;
&lt;div id=&quot;foo&quot; popover&gt;Popover content&lt;/div&gt;
</code></pre>
<p>但兼容性堪忧，不建议立即使用。</p>
<h3><code>inert</code> 属性</h3>
<blockquote>
<p>没听说过，感兴趣</p>
</blockquote>
<p><code>inert</code> 属性是一个于 2022 年发布的 HTML 全局属性，它可以让元素暂时失效，使其在用户界面中不可交互。设置 <code>inert</code> 的元素及其子元素将不会响应用户的点击、焦点、滚动等交互行为，并且不会被辅助技术（如屏幕阅读器）关注到。</p>
<p>也就是说，使用模态框或者某些弹窗时可以控制默认界面不受影响了，如此一来就可以解决以往需要处理的页面滚动造成的影响的需求了。</p>
<p>此外，也可以给某个正在加载的内容添加此属性，以免触发某些未准备好的交互逻辑。</p>
<h3>Clipboard  API</h3>
<blockquote>
<p>用过，没兴趣</p>
</blockquote>
<p><code>Clipboard API</code>提供一个异步读写剪贴板的接口（仅支持在安全的上下文中使用，例如 HTTPS）：</p>
<pre><code class="language-js">navigator.clipboard
  .readText()
  .then(
    (clipText) =&gt; (document.querySelector(&quot;.editor&quot;).innerText += clipText),
  );
</code></pre>
<p>剪贴板管理依然推荐使用 <code>clipboard.js</code>这样的库，它能解决太多兼容性问题，节省时间和精力。</p>
<h2>内容</h2>
<h3>懒加载</h3>
<blockquote>
<p>用过，感兴趣</p>
</blockquote>
<p>诸多网络相关的资源都可以设置懒加载状态，即仅在需要时加载某些资源。</p>
<p>在具有大量图片或视频、iframe 之类的页面，非常适合使用懒加载属性，让用户滚动到可见区域再加载资源。</p>
<pre><code class="language-html">&lt;img src=&quot;picture.jpg&quot; loading=&quot;lazy&quot; /&gt;
&lt;iframe src=&quot;supplementary.html&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
</code></pre>
<p>通常情况下，这样的处理方案已经足够了。但是，如果想更精细控制何时加载资源，滚动距离多远时加载资源，则推荐使用 <code>Intersection Observers</code>接口。</p>
<h3>srcset 和 sizes 属性</h3>
<blockquote>
<p>用过，感兴趣</p>
</blockquote>
<p>这两个属性可以帮助浏览器在不同的宽度下选择不同的图片资源。</p>
<pre><code class="language-html">&lt;img
  srcset=&quot;fairy-med.jpg 480w, fairy-large.jpg 800w&quot;
  sizes=&quot;(max-width: 600px) 480px, 800px&quot;
  src=&quot;fairy-large.jpg&quot;
  alt=&quot;Elva dressed as a fairy&quot; /&gt;
</code></pre>
<h3>资源提示</h3>
<blockquote>
<p>没听说过，感兴趣</p>
</blockquote>
<p><code>link</code>标签支持设置 <code>rel</code>提示来提升某些资源的加载权重。</p>
<pre><code class="language-html">&lt;link rel=&quot;preload&quot; href=&quot;picture.jpg&quot; /&gt;
&lt;link rel=&quot;dns-prefetch&quot; href=&quot;https://fonts.googleapis.com/&quot; /&gt;
</code></pre>
<h3>Content-Security Policy (CSP)</h3>
<blockquote>
<p>用过，感兴趣</p>
</blockquote>
<p>在 <code>html</code>头部的 <code>meta</code>属性中添加内容安全策略，可以防止跨站脚本攻击。网站所有者可以告诉浏览器哪些资源可以被加载到其网页中，从而限制潜在的安全风险。</p>
<pre><code class="language-html">&lt;meta http-equiv=&quot;Content-Security-Policy&quot; content=&quot;default-src &#39;self&#39;; script-src &#39;self&#39; https://cdn.example.com&quot;&gt;
</code></pre>
<p>如上所示，浏览器仅加载当前域名和 <code>https://cdn.example.com</code>的脚本、字体、图片、样式表网络资源。</p>
<h3>fetchpriority attribute</h3>
<blockquote>
<p>没听过，感兴趣</p>
</blockquote>
<p><strong>fetchpriority</strong> 属性是 HTML5 引入的一个新属性，它用于告诉浏览器，在加载页面资源时，应该优先加载哪些资源。简单来说，就是给资源分配了一个优先级，让浏览器在有限的网络资源下，能够更有效地分配资源，从而提升页面的加载速度和用户体验。</p>
<pre><code class="language-html">&lt;img src=&quot;logo.svg&quot; fetchpriority=&quot;high&quot; /&gt;
</code></pre>
<p>这个属性是全局属性，可以和 <code>script</code>标签的 <code>defer</code>或者 <code>async</code>配合，同时控制资源的加载和执行时机以及资源优先级。也可以配合 <code>link</code>的 <code>rel</code>属性来控制资源的预先加载和空闲时预先加载未来可能用到的资源。</p>
<p>字体、样式表、脚本、接口数据等等，都是可以控制加载时机、优先级的。组合使用这些技巧可以让你的 web 应用的用户体验得到提升（这需要一定的经验，否则过度使用这些属性可能会导致资源浪费，影响页面加载速度）。</p>
<h3>blocking=&quot;render&quot;</h3>
<blockquote>
<p>没听过，感兴趣</p>
</blockquote>
<p><code>script</code>的 <code>blocking=&quot;render&quot;</code> 是一个相对较新的 HTML 属性，主要用于控制资源加载对页面渲染的影响。当一个资源被赋予 <code>blocking=&quot;render&quot;</code> 属性时，浏览器会在加载和解析该资源的过程中暂停页面的渲染。</p>
<pre><code class="language-html">&lt;script blocking=&quot;render&quot; async src=&quot;async-script.js&quot;&gt;&lt;/script&gt;
</code></pre>
<p>这个属性是为了解决某些因关键样式文件未完全加载就渲染页面，导致样式闪烁或者布局错乱的问题。</p>
<p>仅使用 <code>async/defer/preload</code>只会控制加载资源和执行的时机，不会阻塞页面渲染。</p>
<h3>用于 AR/VR/3D 内容的 model</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<p>允许将 3D 图形内容嵌入到 HTML 中。</p>
<pre><code class="language-html">&lt;model src=&quot;3d-assets/car&quot;&gt;&lt;/model&gt;
</code></pre>
<p>没有接触过此类技术和问题，大吃一惊。</p>
<h3>controlslist attribute</h3>
<blockquote>
<p>没听说过，感兴趣</p>
</blockquote>
<pre><code class="language-html">&lt;video src=&quot;fun.mp4&quot; controlslist=&quot;nodownload&quot;&gt;&lt;/video&gt;
</code></pre>
<p>视频类媒体标签，可以设置控制列表来控制是否显示默认的控件，上述代码则禁止显示下载按钮，当然也可以添加或隐藏自定义的按钮。</p>
<h3>CSS Custom Highlight API</h3>
<blockquote>
<p>没听过，感兴趣</p>
</blockquote>
<p>CSS Custom Highlight API 是一组 CSS 属性，它允许开发者自定义文本高亮时的外观。通过这个 API，我们可以更精细地控制文本高亮的颜色、背景、边框等样式，从而打造出更具个性化的用户界面。</p>
<pre><code class="language-js">// css
::highlight(my-custom-highlight) {
  background-color: blue;
}
::-moz-selection { /* Firefox */
  background: #ffcc00;
  color: #fff;
}

// js
const parentNode = document.getElementById(&quot;foo&quot;);
const range1 = new Range();
range1.setStart(parentNode, 10);
range1.setEnd(parentNode, 20);
const highlight = new Highlight(range1);
CSS.highlights.set(&quot;my-custom-highlight&quot;, highlight);
</code></pre>
<p><code>CSS.highlights</code>和 <code>Highlight</code>对象，它们允许更精细地控制文本的高亮显示。你可能见过使用 <code>&lt;span&gt;</code>标签包裹文本并应用CSS类的方法，两者各有优缺点，并且这是一个较新的 API，还得考虑兼容性问题。</p>
<h3>setHtmlUnsafe()</h3>
<blockquote>
<p>没听过，感兴趣</p>
</blockquote>
<p>用于将 HTML 字符串解析为文档片段，然后替换 DOM 中元素的子树。</p>
<pre><code class="language-js">const value = &quot;&lt;p&gt;This is a string of text&lt;/p&gt;&quot;;
document.getElementById(&quot;target&quot;).setHTMLUnsafe(value);
</code></pre>
<p>某种意义上来说，这种方法难以维护，且容易产生 <code>XSS</code>漏洞，但是在很多时候都非常有用，我们确实需要这种方式去渲染一些不安全的内容。</p>
<p>这个 API 是 2024 年发布的，目前兼容性堪忧，其发布是为了解决 <code>Element.inserHTML</code>存在的潜在问题：丢失 <code>Shadow DOM</code>：如果插入的 HTML 字符串包含 <code>&lt;div&gt;</code>元素，这些元素的属性（如 <code>shadowroot</code>）可能会被忽略或丢失。它们不会在 DOM 中被解析为 Shadow DOM，导致无法实现预期的封装和样式。</p>
<blockquote>
<p><code>Shadow DOM</code> 是一种将 DOM 元素封装起来的技术，它允许创建与主文档 DOM 隔离的独立 DOM 树。这样可以确保封装的元素及其样式不与页面的其他部分冲突，也不会受到外部 CSS 或 JavaScript 的影响。它是 Web 组件（Web Components）技术的重要组成部分。</p>
</blockquote>
<p>使用 <code>Shadow DOM</code>可以创建独立于主文档的 DOM 树的节点，其样式与外部样式表无关联，非常适合创建自定义的组件。</p>
<p>常见的应用场景有：Chrome 插件开发中的 <code>Content</code>内容，即在页面添加插件的节点和样式，避免影响主文档，也避免被主文档的样式影响。</p>
<h3>parseHtmlUnsafe()</h3>
<blockquote>
<p>没听说过，不感兴趣</p>
</blockquote>
<p>同样是今年发布的 <code>API</code>，用于解析 HTML 字符串（可能包含声明性影子根），以创建新的 Document 实例。</p>
<h3>Intl.Segmenter</h3>
<blockquote>
<p>没听说过，不感兴趣</p>
</blockquote>
<p>这是一个较新的国际化 <code>API</code>，用于根据语言的特定规则对文本分段。</p>
<h2>Web 组件</h2>
<h3>template</h3>
<blockquote>
<p>听说过，感兴趣</p>
</blockquote>
<p><code>&lt;template&gt;</code>是 <code>HTML</code>中用于定义一段可重用、延迟渲染的内容的元素，通常用于 <code>Web Component</code>！<code>&lt;template&gt;</code> 在 Web 组件开发中经常与 <strong>Shadow DOM</strong> 结合使用。可以将模板的内容插入到 Shadow DOM 中，实现更好的封装和样式隔离。</p>
<pre><code class="language-html">&lt;template id=&quot;my-template&quot;&gt;
  &lt;style&gt;
    .card {
      background-color: #f9f9f9;
      padding: 1rem;
    }
  &lt;/style&gt;
  &lt;div class=&quot;card&quot;&gt;
    &lt;h2&gt;Title&lt;/h2&gt;
    &lt;p&gt;Shadow DOM content&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;div id=&quot;my-component&quot;&gt;&lt;/div&gt;

&lt;script&gt;
  class MyComponent extends HTMLElement {
    constructor() {
      super();
      const shadowRoot = this.attachShadow({ mode: &#39;open&#39; });
      const template = document.getElementById(&#39;my-template&#39;);
      const content = template.content.cloneNode(true);
      shadowRoot.appendChild(content);
    }
  }

  customElements.define(&#39;my-component&#39;, MyComponent);
&lt;/script&gt;

&lt;my-component&gt;&lt;/my-component&gt;
</code></pre>
<p>在流行的组件库 <code>React/Vue/Angular</code>之外，原生的 <code>Web Component</code>依然存在着生存空间。</p>
<p>原生的 <code>Web Component</code>的优势在于”无框架依赖“，即可以在 React、Vue、Angular、Svelte 甚至原生 HTML 中使用，不存在依赖某个特定框架的锁定问题。</p>
<p>通过这个技术开发一些轻量级的库，看起来非常方便。</p>
<p>或许，<code>未来是你的</code>。</p>
<h3>使用自定义元素</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<pre><code class="language-html">&lt;sl-switch&gt;Switch&lt;/sl-switch&gt;
</code></pre>
<p>可以在原生的 <code>HTML</code>中使用自己或他人定义的自定义元素。</p>
<h3>定义自定义元素</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<pre><code class="language-html">class MyElement extends HTMLElement { … }
customElements.define(&quot;my-element&quot;, MyElement);
</code></pre>
<h3>作用域自定义元素注册表</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<pre><code class="language-js">const registry = new CustomElementRegistry();
registry.define(&#39;sub-element&#39;, SubElement);
</code></pre>
<p>作用域自定义元素注册表（Scoped Custom Element Registries）是一个新的 Web API，用于管理和定义在特定作用域内的自定义元素注册表。它通过为不同的文档片段（例如，Shadow DOM）或组件提供独立的自定义元素注册表，解决了多个组件或微前端应用中自定义元素命名冲突的问题。</p>
<p>传统的自定义元素都是全局的，即通过 <code>customElements.define()</code>来注册到全局注册表，如此一来即会导致命名冲突和隔离性不足的问题，在微前端架构场景下尤为明显，因此每一个 <code>DOM</code>树或者 <code>Shadow DOM</code>都急需自己的自定义元素注册表。</p>
<h3>Shadow DOM</h3>
<blockquote>
<p>用过，感兴趣</p>
</blockquote>
<p>这是一种将组件的样式和结构与外部文档隔离的方式。</p>
<pre><code class="language-js">class MyElement extends HTMLElement {
  constructor() {
    super();
  
    // 创建一个 Shadow DOM 并附加到当前元素上
    const shadowRoot = this.attachShadow({ mode: &#39;open&#39; });
  
    // 向 Shadow DOM 中添加内容
    shadowRoot.innerHTML = `
      &lt;style&gt;
        p {
          color: red;
        }
      &lt;/style&gt;
      &lt;p&gt;This is a shadow DOM content!&lt;/p&gt;
    `;
  }
}

customElements.define(&#39;my-element&#39;, MyElement);
</code></pre>
<p>在这个例子中，<code>&lt;my-element&gt;</code> 内部的内容被封装在一个 Shadow DOM 中，外部页面无法直接访问或修改它的内部结构和样式。</p>
<h3>Declarative Shadow DOM</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<p>通过 <code>HTML</code>定义 <code>Shadow</code>树！</p>
<pre><code class="language-html">&lt;host-element&gt;
    &lt;template shadowrootmode=&quot;open&quot;&gt;
        &lt;!-- Shadow content --&gt;
    &lt;/template&gt;
&lt;/host-element&gt;
</code></pre>
<h3>Named slot assigment</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<p>通过 <code>slot=&quot;slot_name&quot;</code>属性给 <code>Web Component</code>设置插槽，接收外部传入且具有对应 <code>slot</code>属性的内容。</p>
<p>看起来，<strong>Web 组件的命名插槽</strong>与<strong>Vue 插槽</strong>都是通过插槽的概念将外部内容动态插入到组件内部的指定位置，二者有类似的地方。</p>
<p>以下内容都是组件相关，且笔者不了解的内容：</p>
<ul>
<li>命令式插槽分配</li>
<li><code>ElementInternals</code> API</li>
<li>DOM Parts</li>
</ul>
<p>对此不再一一介绍。</p>
<h2>无障碍</h2>
<p>笔者糊了很多代码，但是几乎没做过无障碍的东西。</p>
<h3>Landmark elements</h3>
<blockquote>
<p>用过</p>
</blockquote>
<p>诸如；<code>&lt;main&gt;</code>, <code>&lt;nav&gt;</code>, <code>&lt;aside&gt;</code>, <code>&lt;header&gt;</code>, <code>&lt;footer&gt;</code>, <code>&lt;section&gt;</code> 这样的标签非常常见。</p>
<h3>tabindex 属性</h3>
<blockquote>
<p>没听说过，感兴趣</p>
</blockquote>
<pre><code class="language-html">&lt;div role=&quot;button&quot; tabindex=&quot;0&quot;&gt;I’m Tabbable&lt;/div&gt;
</code></pre>
<p><code>tabindex</code> 属性用于指定 HTML 元素在使用 Tab 键进行页面导航时的顺序。通过设置 <code>tabindex</code> 属性，你可以控制元素是否可以通过 Tab 键获得焦点，以及获得焦点的顺序。</p>
<p><code>&lt;div role=&quot;button&quot;&gt;</code>: 将一个普通的 div 元素赋予了按钮的角色。这告诉屏幕阅读器和其他辅助技术，这个 div 元素应该像一个按钮一样被对待。</p>
<h3>focusgroup attribute</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<p>焦点组是一种新的Web平台功能，允许开发者更好地控制键盘导航和焦点行为。</p>
<pre><code class="language-html">&lt;div focusgroup=&quot;wrap horizontal&quot;&gt;
	&lt;!-- child elements --&gt;
&lt;/div&gt;
</code></pre>
<h3>search 标签</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<p><code>&lt;search&gt;</code>是 2023 年发布的一个通用的搜索标签，语义上将元素内容的用途标识为具有搜索或过滤功能。</p>
<pre><code class="language-html">&lt;search&gt;
  &lt;form action=&quot;search.php&quot;&gt;
    &lt;label&gt;Find: &lt;input name=&quot;q&quot; type=&quot;search&quot;&gt;&lt;/label&gt;
    &lt;button&gt;Go!&lt;/button&gt;
  &lt;/form&gt;
&lt;/search&gt;
</code></pre>
<p>无障碍是一个好的产品应该关注的话题，很遗憾我没有这样的经历。</p>
<h2>类原生应用</h2>
<h3>File System Access API</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<p>文件系统访问 API 可以访问用户本地设备上的文件和目录。</p>
<pre><code class="language-js">const handle = await window.showSaveFilePicker(opts);
</code></pre>
<p>此特性仅支持在某些安全的上下文中使用，例如 <code>HTTPS</code>和 <code>web workers</code>。</p>
<p>访问本地设备的文件系统一直是热门话题，或许浏览器会逐步开放这部分的功能。</p>
<h3>Badging API</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<p>这个 <code>API</code>可以为应用设置数字徽章，简单说就是类似 <code>PWA</code>或者 <code>Electron</code>这样的场景下，可以通过这个 <code>API</code>设置徽章通知。</p>
<p>我找到一篇文章介绍：<a href="https://juejin.cn/post/7129691614385504293">快速了解navigator API setAppBadge在很多客户端应用程序中，一般会通过应用图标的角标来显示当前消息 - 掘金</a></p>
<pre><code class="language-js">navigator.setAppBadge(unreadCount)
</code></pre>
<p>还是非常实用的，也许未来我会用到。</p>
<h3>Web Share API</h3>
<blockquote>
<p>闻所未闻，感兴趣</p>
</blockquote>
<p>打开一个分享窗口，支持通过各种程序分享当前的应用。</p>
<pre><code class="language-js">navigator.share(shareData)
</code></pre>
<p>我记得 <code>vueuse</code>提供了一个钩子来创建快速分享的方案，或许用的就是这个 <code>API</code>!</p>
<p>以下都是 <code>PWA</code>相关的内容：</p>
<ul>
<li>Launch Handler API：允许 <code>PWA</code> 控制它们的启动方式。</li>
<li>File Handling API：允许 <code>PWA</code>  将自身注册为某些文件类型的处理程序。</li>
<li>Window Controls Overlay API：允许 <code>PWA</code>在标题栏区域显示自定义内容，其控件成为覆盖层！太好了，就需要这个！</li>
<li>Isolated Web Apps：本机类似 <code>PWA</code> 的打包、权限和签名更新。</li>
</ul>
<p>PWA 绝对值得投入一些精力！</p>
<h2>其他工具和功能</h2>
<p>以下是一些工具提问，笔者接触过的不多，顺便稍微发出来看看：</p>
<h3>静态或动态网站生成方案</h3>
<ul>
<li>Jekyll</li>
<li>Eleventy</li>
<li>Hugo</li>
<li>Gatsby</li>
<li>Nextjs</li>
<li>Nuxt</li>
<li>Astro</li>
<li>SvelteKit</li>
<li>Remix</li>
</ul>
<h3>提高网站性能的工具</h3>
<ul>
<li>Lighthouse</li>
<li>Browser Devtools</li>
<li>WebPageTest</li>
<li>Pingdom</li>
</ul>
<h3>分析工具</h3>
<ul>
<li>Google Analytics</li>
<li>Plausible Analytics</li>
<li>Fathom Analytics</li>
<li>New Relic</li>
<li>Mixpanel</li>
<li>Clicky</li>
<li>Matomo</li>
<li>Heap</li>
<li>Datadog</li>
</ul>
<h2>最后</h2>
<p>这篇分享到此为止了，我大概花了两小时时间写这篇内容，边写边查相关的知识点，或许未来会在某个场景下想到这部分内容。</p>
<p>先这样，👋🏻</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[周刊：从 2.5D 图标资源到 tailwindcss 混淆]]></title>
            <link>https://i5lin.top/blog/post/2557406192</link>
            <guid isPermaLink="false">2557406192</guid>
            <pubDate>Mon, 30 Sep 2024 18:44:14 GMT</pubDate>
            <description><![CDATA[OK！继续来分享上一周接触到的一些讯息！]]></description>
            <content:encoded><![CDATA[<p>OK！继续来分享上一周接触到的一些讯息！</p>
<h2>2.5D 图标资源站</h2>
<p><img src="https://de4965e.webp.li/blog-images/2024/08/a41c26d458c29dc4366d8c52a3a712ff.png" alt=""></p>
<p><a href="https://www.isocons.app/">Isocons</a> 上分享了很多 2.5D 的 icon，设计师或者独立开发者或许可以看看这样的设计和图标是否能应用到某些产品中去。相对于传统的平面图标来说，使用这种风格的图标会有一种耳目一新的感觉。</p>
<h2>Cloudflare IndexNow</h2>
<p>如果你将应用部署到  Cloudflare 上，那么就可以了解一下 IndexNow 这个工具。IndexNow 可以让搜索引擎快速发现并索引你的新内容或更新。它不仅提高了网站的SEO效果,还节省了爬虫的资源。</p>
<blockquote>
<p>当使用 Cloudflare 的站点更改内容时，Crawler Hints 会为搜索引擎和其他爬网程序提供高质量的数据。这样，爬网程序可以精确地抓取，避免浪费，同时可以普遍减少源服务器及其他 Internet 基础结构的资源消耗。</p>
</blockquote>
<p>这个功能默认是关闭的，但是只需要进入 网站 → 缓存 → 配置，即可找到 Crawler Hints 选项。</p>
<h2>计算机组成原理 - 哈工大</h2>
<p>在网上看到一个哈工大的在线视频，主讲计算机组成原理，地址如下：<a href="https://www.bilibili.com/video/BV1t4411e7LH/?p=1&vd_source=df270fca044b43c4f189a869ad559f9a">https://www.bilibili.com/video/BV1t4411e7LH/?p=1&amp;vd_source=df270fca044b43c4f189a869ad559f9a</a></p>
<h2>Swapy 框架友好的拖动库</h2>
<p><img src="https://pbs.twimg.com/media/GVpaoKnbgAA3qLt?format=jpg&name=medium" alt=""></p>
<p><a href="https://swapy.tahazsh.com/">Swapy – Easy Drag-to-Swap Solution</a>，支持快速在不同的前端框架中引入。主打一个小而美，如果你需要简单的拖动交互，并且期望带有动画效果，那就试试这个库吧。</p>
<h2>TypeScript tips</h2>
<p><img src="https://de4965e.webp.li/blog-images/2024/08/fc7612ef4c8380f31c44c6a478f1adb9.png" alt=""></p>
<p>有时候我们需要声明一个变量的类型是字符串，但是又希望能够自动补全一些固定的字符串。这个问题，我们可以通过上图的第一个例子解决，但是第二个类型声明显然是更好的写法。😄 这也是一个值得加入到你的 TypeScript 武器库中的类型定义。</p>
<pre><code class="language-typescript">type StringWithAutocompleteOptions&lt;T&gt; = T|(string &amp; {})
</code></pre>
<h2>JSON CRACH</h2>
<p><img src="https://github.com/AykutSarac/jsoncrack-vscode/assets/47941171/06715ac1-2403-402f-b3fa-3d91e1c9196a" alt=""></p>
<p><a href="https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode">JSON Crack - Visual Studio Marketplace</a> 是一个强大且创新的 VS Code 扩展，旨在彻底改变与 JSON 数据交互的方式。该扩展无缝地弥合了数据和可视化之间的差距，使开发人员、数据分析师和爱好者能够轻松地将复杂的 JSON 数据集转换为信息丰富且具有视觉吸引力的图表。</p>
<p>说人话就是：这个 vscode 插件能在你打开一个 JSON  文件的时候提供一个美观的可视化效果，你可以拖动和预览深层次数据，查看数据之间的关系。</p>
<p>如果你不喜欢插件，作者也开发了 web 版本。</p>
<h2>Moffee</h2>
<p><img src="https://raw.githubusercontent.com/BMPixel/moffee/main/docs/images/moffee-example.png" alt=""></p>
<p><a href="https://github.com/BMPixel/moffee?tab=readme-ov-file">BMPixel/moffee: moffee: Make Markdown Ready to Present</a> 是一个开源幻灯片制作工具，可将 Markdown 文档转换为干净、专业的幻灯片。</p>
<p>我记得去年转正答辩的时候我直接用 markdown 文档展示，其他人都是 PPT  😂，看起来 Leader 不是很满意 markdown doc。</p>
<h2>Nextjs 开发库</h2>
<p>最近在用 Nextjs 写一些东西，用到了以下两个不错的库：</p>
<ul>
<li><a href="https://github.com/pacocoursey/next-themes?tab=readme-ov-file">pacocoursey/next-themes: Perfect Next.js dark mode in 2 lines of code. Support System preference and any other theme with no flashing</a> 主题切换（系统、dark 模式和 light 模式）解决方案</li>
<li><a href="https://github.com/amannn/next-intl">amannn/next-intl: 🌐 Internationalization (i18n) for Next.js</a> 国际化解决方案（基于 app 结构的路由解决方案）</li>
</ul>
<h2>12FT</h2>
<p><a href="https://12ft.io/">https://12ft.io/</a> 是一个穿透文章付费墙的应用，支持粘贴一个链接，尝试穿透付费墙，返回原文内容。</p>
<p>常见的链接支持 medium.com 这类为了提高 google seo 排行而提供了特殊通道的站点，谷歌的搜索引擎爬虫可以爬取到付费内容。</p>
<h2>混淆 tailwindcss</h2>
<p><a href="https://github.com/soranoo/next-css-obfuscator">soranoo/next-css-obfuscator: A package deeply inspired by PostCSS-Obfuscator but for Next.js.</a> 是一款混淆 nextjs 项目 tailwindcss 的插件，如果你想要混淆代码的类名，以免被直接复制走，可以用这个库。</p>
<h2>FontSource</h2>
<p><a href="https://fontsource.org/">Fontsource</a> 是开源字体的集合，它们被打包到单独的 NPM 包中，以便在 Web 应用程序中自托管，我们可以直接在这里检索自己需要的开源字体，然后通过 npm 的操作将字体引入。</p>
<h2>Editor.js</h2>
<p><a href="https://github.com/codex-team/editor.js">codex-team/editor.js: A block-style editor with clean JSON output</a> 是一个现代化的开源 markdown 编辑器，开发者可以轻松通过这个工具构建一个轻量的 notion 风格的文本编辑器。笔者体验下来最大的感受就是颜值超高，编辑体验拉满，我直接就用来代替了从前用的 <code>vditorjs</code>。</p>
<h2>AI Background Remover</h2>
<p><a href="https://html.zone/background-remover">HTML.ZONE</a> 提供了一个纯浏览器端的图片去背景功能的应用，开发者可以体验体验，作者提供了开源代码，对 Transformers.js 和 AI 结合的东西感兴趣的话，可以看看其实现思路。</p>
<h2>preline UI</h2>
<p><img src="https://preline.co/assets/img/og-image.png" alt="Preline UI"></p>
<p><a href="https://preline.co/index.html">Preline UI - Tailwind CSS components library</a> 是一个纯 tailwindcss 和 HTML 实现的组件公开网站，开发者可以轻松选取自己喜欢的组件，直接复制到 tailwindcss 项目中使用。</p>
<h2>Tiged</h2>
<p><a href="https://www.npmjs.com/package/tiged">tiged - npm</a> 可以轻松下载 Github 仓库的数据，常见于实现一些脚手架。</p>
<h2>Email.ML</h2>
<p><a href="https://email.ml/">Email.ML - Minimalist Temporary Email</a> 是基于 cloudflare 平台的免费临时邮件应用，可以在某些时候传递一些信息。</p>
<h2>react-slot-counter</h2>
<p><a href="https://github.com/almond-bongbong/react-slot-counter">almond-bongbong/react-slot-counter: 🚀 Elevate Your UI with Dynamic, Eye-Catching Counters</a> 具有数字变化效果的动画组件。</p>
<h2>Fetch 😂</h2>
<p><img src="https://pbs.twimg.com/media/GS--KSHakAAHl0I?format=jpg&name=large" alt="fetch"></p>
<h1>最后</h1>
<p>最后放几张好玩的图</p>
<p><img src="https://pbs.twimg.com/media/GSLoWeBbcAAZJpF?format=jpg&name=900x900" alt=""></p>
<p>最近开始准备找工作的事情了，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[TypeScript 中的 never、unknown 的使用时机]]></title>
            <link>https://i5lin.top/blog/post/2511378330</link>
            <guid isPermaLink="false">2511378330</guid>
            <pubDate>Sat, 07 Sep 2024 01:51:47 GMT</pubDate>
            <description><![CDATA[最近面试时被提问到 TypeScript 相关的知识，其中一个问题就是：什么时候应该在 TypeScript 中使用 never 和 unknown。]]></description>
            <content:encoded><![CDATA[<p><img src="https://de4965e.webp.li/blog-images/2024/09/1d13fb693096a714f5a96a44fc567a22.png" alt=""></p>
<p>最近面试时被提问到 TypeScript 相关的知识，其中一个问题就是：什么时候应该在 TypeScript 中使用 <code>never</code>和 <code>unknown</code>。</p>
<p>笔者觉得自己没答好，于是再次学习了相关知识分享下来。本文不是 TypeScript types 的科普，所以别着急，只需一步一个脚印前进即可。</p>
<h2>TypeScript 类型浅谈</h2>
<p>首先，我们要明确什么是 TypeScript 的类型。</p>
<p>简单说，<code>string</code>类型即 TypeScript 中字符串变量的类型集合。同理，<code>number</code>是数字变量的类型集合，当我们写下如下代码时：</p>
<pre><code class="language-typescript">const stringOrNumber: strig | number = &#39;1&#39;;
</code></pre>
<p>联合类型 <code>string | number</code>应运而生，这个联合类型就是字符串类型集合和数字类型集合的并集。</p>
<p>我们在 TypeScript 中可以把字符串类型或数字类型的变量赋值给 <code>stringOrNumber</code>，除此之外还能把什么类型的变量赋值给 <code>stringOrNumber</code>呢？</p>
<p>答案是：</p>
<ul>
<li><code>never</code></li>
<li><code>any</code></li>
</ul>
<blockquote>
<p>😁 我们不谈 <code>any</code>类型，至少在离职之前不谈 <code>any</code>类型，也不在项目中使用 <code>any</code>类型（如果加班很多当我没说）。</p>
</blockquote>
<p><code>never</code>类型是一个空集，它处于类型系统的最内层。</p>
<p>当我们定一个变量的类型是 <code>unknown</code>时，我们实际上需要的就是一个未知类型的值，举个例子：</p>
<pre><code class="language-typescript">let foo: unknown;
foo = stringOrNumber; // ok

function demo(param: unknown) {
  // ...
  if(typeof param === &#39;string&#39;) {
    // ...
  }
}
demo(stringOrNumber); // ok
</code></pre>
<p><code>unknown</code>是所有类型集合的超集，<code>never</code>则是所有类型的子集（bottom type）。</p>
<h2>never 类型浅析</h2>
<p>来点代码，再谈其他：</p>
<pre><code class="language-typescript">function asyncTimeout(ms: number): Promise&lt;never&gt; {
  return new Promise((_, reject) =&gt; {
    setTimeout(() =&gt; reject(new Error(`Async Timeout: ${ms}`)), ms)
  })
}
</code></pre>
<p>上述代码是一个异步超时的函数，其返回值类型是 <code>Promise&lt;never&gt;</code>，接着我们继续实现一个支持超时的请求函数：</p>
<pre><code class="language-typescript">function fetchData(): Promise&lt;{ price: number }&gt; {
  return new Promise((resolve) =&gt; {
    setTimeout(() =&gt; {
      resolve({ price: 100 });
    }, Math.random() * 5000);
  });
}

async function fetchPriceWithTimeout(tickerSymbol: string): Promise&lt;number&gt; {
  const stock = await Promise.race([
    fetchData(), // returns `Promise&lt;{ price: number }&gt;`
    asyncTimeout(3000), // returns `Promise&lt;never&gt;`
  ]);
  // stock is now of type `{ price: number }`
  return stock.price;
}
</code></pre>
<blockquote>
<p><code>Promise.race</code> 是 JavaScript 中 <code>Promise</code> 对象的一个静态方法，它用于处理多个 Promise 实例，并返回一个新的 Promise 实例。这个新的 Promise 实例会在第一个传入的 Promise 实例完成或拒绝时完成或拒绝。</p>
</blockquote>
<p><code>fetchData</code>是模拟的数据请求函数，实际开发中可以是某个接口请求，TypeScript 编译器将 <code>race</code>函数的返回值类型视为 <code> Promise&lt;{price: number}&gt; | Promise&lt;never&gt;</code>即 <code>stock</code>的类型为 <code>{price: number}</code>，这就是 <code>never</code>类型的使用场景实例。</p>
<p>再来一个代码例子（来源于网络）：</p>
<pre><code class="language-typescript">type Arguments&lt;T&gt; = T extends (...args: infer A) =&gt; any ? A : never
type Return&lt;T&gt; = T extends (...args: any[]) =&gt; infer R ? R : never

function time&lt;F extends Function&gt;(fn: F, ...args: Arguments&lt;F&gt;): Return&lt;F&gt; {
  console.time();
  const result = fn(...args);
  console.timeEnd();
  return result;
}

function add(a: number, b: number): number {
  return a + b;
}

const sum = time(add, 1, 2); // 调用 time 函数测量 add 函数的执行时间

console.log(sum); 
// 输出：
// [0.09ms] default
// 3
</code></pre>
<p>上述示例使用了 <code>never</code>类型来缩窄条件类型的结果。在 <code>type Arguments&lt;T&gt;</code>中，如果传入的泛型 <code>T</code>是一个函数，则将从其参数的类型生成一个推断类型 <code>A</code>作为满足条件时的类型。也就是说，如果我们传入的不是函数，那么TypeScript 编译器最后就会得到 <code>never</code>类型兜底。</p>
<p>于是乎在 <code>time</code>函数定义的时候就可以使用这两个带泛型的条件类型定义，最终让 <code>time</code>函数能够通过传入的函数 <code>add</code>的类型来推导最终的返回值类型：<code>sum</code>是 <code>number</code>类型。</p>
<blockquote>
<p>我不确定大家工作中是否要写这些类型定义，但这就是 TypeScript。</p>
</blockquote>
<p>我们可以在<a href="https://github.com/Microsoft/TypeScript/blob/eb2297df022f6b1f284aff96e93c06097bb01ea5/lib/lib.es5.d.ts#L1459">官方源代码</a>中看到类似的类型声明：</p>
<pre><code class="language-typescript">/**
 * Exclude null and undefined from T
 */
type NonNullable&lt;T&gt; = T extends null | undefined ? never : T;
</code></pre>
<p>这里的条件类型对于传入泛型是联合类型的时候，会将判断类型分发到每一个到联合类型。</p>
<pre><code class="language-typescript">// if T = A | B

T extends U ? X : T == (A extends U ? X : A) | (B extends U ? X : B)
</code></pre>
<p>所以 <code>NonNullable&lt;string | null&gt;</code>的解析逐层如下：</p>
<pre><code class="language-typescript">NonNullable&lt;string | null&gt;
  // The conditional distributes over each branch in `string | null`.
  == (string extends null | undefined ? never : string) | (null extends null | undefined ? never : null)

  // The conditional in each union branch is resolved.
  == string | never

  // `never` factors out of the resulting union type.
  == string
</code></pre>
<p>最终编译器识别其为 <code>string</code>类型。</p>
<h2>unknown 类型浅析</h2>
<p>任何类型的值都可以赋值给 <code>unknown</code>类型的变量，因此我们可以在考虑使用 <code>any</code>的时候优先考虑 <code>unknown</code>，一旦我们选择使用 <code>any</code>，也就意味着我们主动关闭了 <code>TypeScript</code>的类型安全保护。</p>
<p>好了，下面来看一个代码示例：</p>
<pre><code class="language-typescript">function prettyPrint(x: unknown): string {
  if (Array.isArray(x)) {
    return &quot;[&quot; + x.map(prettyPrint).join(&quot;, &quot;) + &quot;]&quot;
  }
  if (typeof x === &quot;string&quot;) {
    return `&quot;${x}&quot;`
  }
  if (typeof x === &quot;number&quot;) {
    return String(x)
  } 
  return &quot;etc.&quot;
}
</code></pre>
<p>这是一个美观的打印输出函数，在调用时可以传入任意类型的参数。但是在传入后我们需要手动去缩窄类型，才能在逻辑内部使用特定类型变量的方法。</p>
<p>此外，如果数据来源不明确时，也可以促使开发者进行必要的类型验证和类型检查，减少运行时错误。</p>
<p>另外，笔者还见到了如下所示的类型守卫示例，稍微再分享一下：</p>
<pre><code class="language-typescript">function func(value: unknown) {
  // @ts-expect-error: Object is of type &#39;unknown&#39;.
  value.test(&#39;abc&#39;);

  assertIsRegExp(value);

  // %inferred-type: RegExp
  value;

  value.test(&#39;abc&#39;); // OK
}

/** An assertion function */
function assertIsRegExp(arg: unknown): asserts arg is RegExp {
  if (! (arg instanceof RegExp)) {
    throw new TypeError(&#39;Not a RegExp: &#39; + arg);
  }
}
</code></pre>
<p>对于未知类型的参数，可以使用 <code>assertIsRegExp</code>函数来断言类型，在其内部如果传入的参数符合类型的话，就不会抛出错误。在 <code>func</code>下半部分编译器则收到了断言信号，就可以直接使用 <code>test</code>方法了。</p>
<p>这种写法跟 <code>if/else</code>的区别在于开发者写 <code>if/else</code>时需要显式地书写类型不在预期时的代码，否则不满足类型时可能缺少运行信息，导致正确类型条件下的代码不执行。</p>
<p>我们没法确保开发者一定会写类型不在预期时的处理逻辑，因此使用类型断言守卫也是一种可行之策，在运行时将会报错，如果前端项目运行在有效的监控体系下，也能收到错误信息以便于排查。</p>
<p>如果你喜欢这种类型守卫，可以使用<a href="https://github.com/elierotenberg/typed-assert#readme">elierotenberg/typed-assert: A typesafe TS assertion library</a>这样的库来减少工作量，它帮我们写了 <code>assertIsRegExp</code>这样的函数。</p>
<p>如果你喜欢显式的 <code>if/else</code>条件判断，也可以使用<a href="https://github.com/mesqueeb/is-what">mesqueeb/is-what: JS type check (TypeScript supported) functions like `isPlainObject() isArray()` etc. A simple &amp; small integration.</a>这样的超轻量类型检查库。</p>
<p>如果你不想引入其他库，那么只能自己写类型判断代码了，但是自己写的话可要注意了，来看看如下代码：</p>
<pre><code class="language-typescript">// 1)
typeof NaN === &#39;number&#39; // true
// 🤔 (&quot;not a number&quot; is a &quot;number&quot;...)

// 2)
isNaN(&#39;1&#39;) // false
// 🤔 the string &#39;1&#39; is not-&quot;not a number&quot;... so it&#39;s a number??

// 3)
isNaN(&#39;one&#39;) // true
// 🤔 &#39;one&#39; is NaN but `NaN === &#39;one&#39;` is false...
</code></pre>
<p>这只是一个 <code>NaN</code>特例，但<strong>这就是 JavaScript</strong>。</p>
<p>好了，下次见。</p>
<h2>参考</h2>
<ul>
<li><a href="https://blog.logrocket.com/when-to-use-never-unknown-typescript/">When to use never and unknown in TypeScript - LogRocket Blog</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html">TypeScript: Documentation - TypeScript 3.0</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/basic-types.html">TypeScript: Handbook - Basic Types</a></li>
<li><a href="https://exploringjs.com/tackling-ts/toc.html">Tackling TypeScript</a></li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[工具推荐：高效的开发者工具 He3]]></title>
            <link>https://i5lin.top/blog/post/2116380682</link>
            <guid isPermaLink="false">2116380682</guid>
            <pubDate>Sat, 03 Feb 2024 09:22:17 GMT</pubDate>
            <description><![CDATA[Hello，大家好 ！今天给大家分享一款开发工具箱 He3。]]></description>
            <content:encoded><![CDATA[<p>Hello，大家好 ！今天给大家分享一款开发工具箱 <code>He3</code>。</p>
<p><code>He3</code> 是一款开放、高效的开发者工具箱，到目前为止已经集成了超过 <code>500+</code>工具。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20240203154816467.png" alt="image-20240203154816467"></p>
<p>笔者第一次接触这个产品的时候，他们的产品还处于早期阶段，甚至还在招聘开发人员，官网和产品都做得比较粗糙，但是核心的几个功能已经完备了。</p>
<p>在配置新电脑的时候，我想起这个工具，于是进入了他们的官网准备下载客户端，无意间发现他们 <code>升级了</code>，让我眼前一亮，这也是我重新分享这个工具的原因。</p>
<p><img src="https://he3app.com/section/bg01.png" alt=""></p>
<p>对于开发人员来说，我很满意他们以下的几个为我所用的核心功能：</p>
<h3>JSON 相关</h3>
<ul>
<li><code>JSON</code> 转 <code>TypeScript</code> 类型定义：日常开发中，我需要将后端同事的接口实例的数据重新定义为前端可用的接口类型，这个功能大大提高了我的效率。</li>
<li><code>JSON</code>格式比较：在某些场景下我需要对比两个 JSON 数据结构，这个功能非常有用，我不在需要其他独立的文件比较工具就能做简单的处理。</li>
<li><code>JSON</code>查看器</li>
</ul>
<p>此外，新版本还支持了一系列跟 <code>JSON</code>相关的功能供用户选用。</p>
<h3>其他方向</h3>
<ul>
<li><p>文本处理</p>
</li>
<li><p>编解码和加密解密</p>
</li>
<li><p>图像处理</p>
<ul>
<li>证件照编辑</li>
<li>图片转图标</li>
<li>图片裁剪</li>
<li>水印</li>
<li>背景消除</li>
<li>格式转换</li>
<li>压缩</li>
</ul>
</li>
<li><p>颜色选择、转换</p>
</li>
<li><p>网络管理</p>
<ul>
<li>端口管理</li>
<li>短链接生成</li>
<li>用户代理解析器</li>
<li>等等</li>
</ul>
</li>
<li><p>音频处理（省去了我单独使用 FFMPEG 处理的需求）</p>
<ul>
<li>合并</li>
<li>压缩</li>
<li>转GIF</li>
<li>截取</li>
<li>调速</li>
<li>格式转换</li>
<li>提取音频</li>
</ul>
</li>
<li><p>日期时间处理</p>
</li>
<li><p>图床</p>
</li>
<li><p>文件共享</p>
</li>
<li><p>AI：做得非常全，但是功能并不出彩</p>
</li>
</ul>
<blockquote>
<p>此应用无广告，预计其主要收入来自于会员订阅，很多需要额外资源的功能（例如图床）都需要积分，每个月免费用户自动获得 100 积分，对我来说还是足够了</p>
</blockquote>
<h3>最后</h3>
<p><code>He3</code>支持多语言，其定价对于国内用户来说感觉还是不便宜的，尤其是最抠门（贫穷）的国内普通开发者来说。</p>
<p>此外，很开心开发团队还设置了 <code>GitHub</code>讨论组，用于收集用户意见和建议，我们可以很轻松地联系上开发团队，并建言献策，我想这也是这个工具变得越来越好的原因之一。</p>
<p>好了，我们下次见。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[从 Infinite Grid 瀑布流到自带高亮的魔改字体！]]></title>
            <link>https://i5lin.top/blog/post/2473732932</link>
            <guid isPermaLink="false">2473732932</guid>
            <pubDate>Mon, 19 Aug 2024 16:19:24 GMT</pubDate>
            <description><![CDATA[这篇周刊非常特殊，特殊之处在于我开发了一个页面来专门写和改博客文章。]]></description>
            <content:encoded><![CDATA[<p>Hello 大家好，下面为大家介绍上周笔者接触到的一些新的讯息。</p>
<h2>React 组件</h2>
<p>有时候我不需要完整的 React 组件库，而是仅安装特定的组件。下面是我在日常开发中发现的<code>react 小而美组件</code>:</p>
<ul>
<li><a href="https://react-select.com/home">React Select</a> 选择器组件，API 非常简洁，但是可扩展性做得很好</li>
<li><a href="https://react-hot-toast.com/">react-hot-toast - The Best React Notifications in Town</a> 通知信息组件，小巧易用</li>
</ul>
<h2>Vditor 编辑器</h2>
<p><img src="https://b3log.org/vditor/images/editor.png" alt=""></p>
<p><a href="https://b3log.org/vditor/">Vditor - 一款浏览器端的 Markdown 编辑器，支持所见即所得（富文本）、即时渲染（类似 Typora）和分屏预览模式</a>，支持原生和常见前端框架无缝接入。</p>
<h2>JavaScript 自学教程</h2>
<p><a href="https://tsejx.github.io/javascript-guidebook//">JavaScript Guidebook</a> 是一个包含了完整体系的开发者教程指南，内容涉及 ECMAScript、DOM、BOM、HTML5、计算机网络等方方面面知识。</p>
<h2>MacOS 菜单栏间隙</h2>
<p><a href="https://sindresorhus.com/menu-bar-spacing">Menu Bar Spacing — Sindre Sorhus</a> 开发的一款专门为 MacOS 菜单栏应用调整间隙的应用，启动后调整间隙之后我的笔记本也能展示全后台应用了。</p>
<p><img src="https://sindresorhus.com/_astro/screenshot1.CzRfO9Kj.jpg" alt=""></p>
<p><img src="https://sindresorhus.com/_astro/screenshot3.DdcwDG7b.jpg" alt=""></p>
<h2>Infinite Grid 瀑布流</h2>
<p><a href="https://naver.github.io/egjs-infinitegrid/">InfiniteGrid</a> 是一个前端框架友好的瀑布流开源解决方案，开发者可以通过文档上的指南在自己喜欢的框架上接入这个瀑布流的库。</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/08/5d70299dd04a7464fe8847786a44dabb.png" alt=""></p>
<p>其特性如下：</p>
<ul>
<li>瀑布流变体<ul>
<li><a href="https://naver.github.io/egjs-infinitegrid/Guides#masonryinfinitegrid">MasonryInfiniteGrid</a> 默认的栅格瀑布流</li>
<li><a href="https://naver.github.io/egjs-infinitegrid/Guides#justifiedinfinitegrid">JustifiedInfiniteGrid</a></li>
<li><a href="https://naver.github.io/egjs-infinitegrid/Guides#frameinfinitegrid">FrameInfiniteGrid</a></li>
<li><a href="https://naver.github.io/egjs-infinitegrid/Guides#packinginfinitegrid">PackingInfiniteGrid</a></li>
</ul>
</li>
<li>TypeScript 友好</li>
<li>API 丰富，支持扩展和自定义</li>
<li>支持 IE9+</li>
<li>支持无限数据插入，支持可视区优化，提高性能</li>
<li>支持数据 loading 机制和骨架渲染</li>
</ul>
<h2>egjs-flicking</h2>
<p><a href="https://naver.github.io/egjs-flicking/">Flicking</a> 是由 <a href="https://naver.github.io/egjs-infinitegrid/">InfiniteGrid</a> 作者开发的另一个开源库，功能上和著名的轮播图库 <a href="https://swiperjs.com/">Swiper - The Most Modern Mobile Touch Slider</a> 很像，都支持多种前端框架和原生 JavaScript 引入，即使是服务端渲染也有完整的 Demo 参考。</p>
<h2>egjs-imready 图片组加载情况检测</h2>
<p><a href="https://naver.github.io/egjs-imready/">I&#39;m Ready to check if the images or videos are loaded!</a> 可以轻松检查指定范围内的图片和视频的加载状态，配合骨架或者针对性的状态过渡渲染非常方便。作者对 TypeScript 的支持很棒，各个前端框架甚至是原生方案都很容易接入。</p>
<h2>内置字体高亮</h2>
<p><a href="https://blog.glyphdrawing.club/font-with-built-in-syntax-highlighting/">GlyphDrawing.Club -blog</a> 这篇博客记录了作者对于小段代码分享的代码高亮解决新方案：通过内置的字体直接渲染代码的颜色。</p>
<p>作者认为通过 <a href="https://prismjs.com/">Prism</a> 或者 <a href="https://highlightjs.org/">highlight.js</a> 这种二次处理的方案不够简单，并且还会生成复杂冗余的 DOM 元素，不符合他的价值观 😄 于是作者修改了一个开源字体<code>Monaspace Krypton</code>，使用 COLR 表和上下文替代功能来实现这一目标。通过为每个字符创建不同颜色的替代字形，作者能够根据代码的语法自动替换字符，从而实现语法高亮。虽然这种方法有其局限性，比如对复杂语法的支持不足，但它为手动编码网站提供了一种简化的 JS/TS 代码渲染解决方案。</p>
<p>如下所示：</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/08/5ddef9fad77a43bf7096f0401560c616.png" alt=""></p>
<p>并且我们可以针对不同的文字进行自定义，例如修改关键字的颜色，注释的颜色等等。</p>
<h2>Windows 录屏软件</h2>
<p><a href="https://www.demoget.com/">DemoGet</a> 正在火热开发中，它也是市面唯一一款支持录屏后自动剪辑的免费工具，支持 4K 导出，非常适合 Windows 下办公的视频媒体从业人员，亦或是有需要录制视频的其他人员。</p>
<blockquote>
<p>我们的目标是让用户在不需要学习复杂的软件操作的情况下，就能够完成录屏剪辑的展示工作。</p>
</blockquote>
<p>其主要亮点如下：</p>
<ul>
<li>自动优化鼠标移动轨迹，强化鼠标移动的展示效果</li>
<li>自动放大微操，提供放大镜头的功能</li>
<li>自动美化背景，让视频更美观</li>
<li>支持录制摄像头，添加字幕，快捷键操作</li>
<li>更多功能正在开发中...</li>
</ul>
<p>对了，作者也在开发 MacOS 版本。</p>
<h2>Favicon.im 获取</h2>
<p><a href="https://favicon.im/">Favicon.im: Instant Website Favicon Fetcher</a> 提供了一种简单的获取指定域名的 favicon 图片的方案（32x32 尺寸的高质量图标），非常适合书签类的应用。此外，作者还写了几篇博客来解释如何将这个方案和暗黑模式切换相结合，以及如何添加到<code>nuxt3</code>这个框架中去。</p>
<p>需要注意的是，如果你的应用使用这个网站的资源，或许应该做一下本地持久化缓存，以免这个站点的变动导致你的应用出问题，亦或是指定一个备选图标作为备用。</p>
<p>不建议大量调用请求数据，以免受到原作者的速率限制的影响，应该配合图片进入可视区域或者接近进入可视区域再渲染。</p>
<h2>简单 Logo 生成</h2>
<p><a href="https://www.logo.surf/">Free Text-to-Logo and Text-to-Favicon Generator</a> 通过配置文本的样式和背景属性，生成一个简单的 Logo 图片资源。如下是一些例子：</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/08/1cf2d8301560080f803ff72899ecbdaa.png" alt=""></p>
<p>不得不说，这些例子非常简单，很适合没啥艺术细胞的开发者，不妨先来一个简单的 Logo ？</p>
<h2>最后</h2>
<p>好了，以上就是上周笔者接触到的一些东西，在这里分享给大家~</p>
<p>ps：依然在找工作，艰难地希望无锡有一些面试机会。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[宫城良田走出漫画，不！他是：河村 勇輝／かわむら ゆうき]]></title>
            <link>https://i5lin.top/blog/post/2461126322</link>
            <guid isPermaLink="false">2461126322</guid>
            <pubDate>Mon, 12 Aug 2024 14:14:00 GMT</pubDate>
            <description><![CDATA[最近一周的状态奇差，大概是跟失业的心理压力有关吧。于是看了看奥运会的篮球比赛，为日本男篮感到骄傲。]]></description>
            <content:encoded><![CDATA[<p>OK！继续来分享上一周接触到的一些讯息！</p>
<h2>IIElevenLabs</h2>
<blockquote>
<p><a href="https://elevenlabs.io/"><a href="https://elevenlabs.io/">ElevenLabs: Free Text to Speech &amp; AI Voice Generator | ElevenLabs</a></a></p>
</blockquote>
<p>这是一个 AI 音频平台，可以使用 AI 音频平台创造最真实的语音。我使用这个平台为<code>GitBatch</code>创建了一段视频的音频，有兴趣的可以去看看:<a href="https://www.youtube.com/watch?v=LJG-CJ00xrU"><a href="https://www.youtube.com/watch?v=LJG-CJ00xrU">I create a small project to batch manage github repos.</a></a>。</p>
<p>简单来说就是可以通过文本来选择不同的声音模型，最后生成拟人的真实语音。</p>
<h2>H5 webview 打包</h2>
<p><a href="https://github.com/Jimmylxue/h5pack-core"><a href="https://github.com/Jimmylxue/h5pack-core">Jimmylxue/h5pack-core: h5pack 是一个跨平台(支持mac/windows)的app打包工具，只需简单的配置和一个指令，即可将 H5 直接打包成 Android APK</a></a>，通过 webview 的能力提前打包资源，具有一定的应用场景。</p>
<h2>票根标签生成器</h2>
<p>朋友开发的微信小程序：票根标签生成器。目前支持常见的饮品连锁品牌的票根图片生成，支持自定义一些关键描述内容。据作者说百分之八十九的用户都是女性，真是让我大开眼界。</p>
<h2>文本转语音工具</h2>
<p><img src="https://de4965e.webp.li/blog-images/2024/08/25238f6ea23d89d3420b4c90628b99fc.png" alt=""></p>
<p><a href="https://luvvoice.com/"><a href="https://luvvoice.com/">Luvvoice: Free Convert Text to Speech Online, No Word Limit</a></a> 是一款功能强大的免费在线文本转语音(TTS)工具。支持70多种语言,200多种自然AI语音,无字数限制,为您的所有TTS需求提供无与伦比的多功能性。</p>
<p><strong>主要特性:</strong>
🌍 支持70+种语言
🎙️ 200+种AI生成的自然语音
📚 无字数限制,可转换整篇文档
🖱️ 用户友好界面,三步即可生成语音
🎧 灵活输出:下载MP3或在线收听
⏯️ 自定义停顿:可在文本中添加停顿以更好地控制语音</p>
<h2>粗略记法效果</h2>
<p><img src="https://camo.githubusercontent.com/33047969ae722fbc3b05f1d507ca600875e7e94b90454253f8f12553829aeb2a/68747470733a2f2f726f7567686e6f746174696f6e2e636f6d2f696d616765732f736f6369616c2e706e67" alt="Rough Notation logo"></p>
<blockquote>
<p>官方文档如下</p>
<ul>
<li><a href="https://github.com/rough-stuff/rough-notation?tab=readme-ov-file"><a href="https://github.com/rough-stuff/rough-notation?tab=readme-ov-file">rough-stuff/rough-notation: Create and animate hand-drawn annotations on a web page</a></a></li>
<li><a href="https://roughnotation.com/"><a href="https://roughnotation.com/">RoughNotation</a></a></li>
</ul>
</blockquote>
<p><code>Rough notation</code>是一个用于给 html 标签添加粗略记法样式动画的库。我在网上看到这样的效果（这是动画效果，用户可以看到这些线条是逐步绘制的，并且可以受代码的控制）：</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/07/fb5fd83866f8158a1261ab3c12adffb4.png" alt=""></p>
<p>于是我便去搜索了一下这个功能如何实现，最终发现已经有人将这个动画效果封装成了公开的库。如你所视，动画完成后会生成一个<code>svg</code>标签。</p>
<h2>PikPak 云盘支持加密货币支付</h2>
<p><img src="https://de4965e.webp.li/blog-images/2024/08/975eeddfdb27f5dcff6393e3d14d5be3.png" alt=""></p>
<p><strong>pikpak</strong>支持加密货币支付意味着越南用户可以把手里的币币用来换取这个新加坡云盘的会员服务，享受超高清、无限制、私密的云盘服务。</p>
<h2>Cloudflare 自建图床</h2>
<p><a href="https://sspai.com/post/90170"><a href="https://sspai.com/post/90170">从零开始搭建你的免费图床系统 （Cloudflare R2 + WebP Cloud + PicGo） - 少数派</a></a> 本文教你如何一步步创建属于自己的图床，并且还对比了国内不同的厂家图床的方案效益。</p>
<h2>复制网站 URL 和 标题</h2>
<p><a href="https://chromewebstore.google.com/detail/copy-title-and-url/ckjejjphgegldelekipaegijidhdjjed?hl=en&authuser=0"><a href="https://chromewebstore.google.com/detail/copy-title-and-url/ckjejjphgegldelekipaegijidhdjjed?hl=en&authuser=0">copy-url-and-title</a></a> 是一款极简的 chrome 插件，通过点击右键菜单项来复制当前标签页的地址和标题内容。</p>
<h2>批量操作 github 仓库</h2>
<p><a href="https://batch-manage-github-repos.i5lin.top/"><a href="https://batch-manage-github-repos.i5lin.top/">GitBatch - Easily manage your Git Hub repositories in bulk</a></a> 是一个通过 github 账户认证来批量操作仓库的应用，如果仅仅需要清理仓库，也可以使用其浏览器插件版本：<a href="https://chromewebstore.google.com/detail/feicmldgdemjfahddoplbbfhmkdbbkho"><a href="https://chromewebstore.google.com/detail/feicmldgdemjfahddoplbbfhmkdbbkho">Batch Delete GitHub repos</a></a>，二者都不需要手动创建 <code>token</code>。</p>
<h2>Seline</h2>
<p><img src="https://seline.so/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fm-dashboard.be4d05e6.png&w=3840&q=75" alt=""></p>
<blockquote>
<p><a href="https://seline.so/"><a href="https://seline.so/">Seline - simple and private website analytics platform</a></a> </p>
</blockquote>
<p>Seline 是一个小而美的网站数据分析平台，仅需一行脚本引入代码即可接入你的应用，数据统计做得很精美。</p>
<h2>最后</h2>
<p><img src="https://de4965e.webp.li/blog-images/2024/07/5f09e2035687cd932b09970972b4d045.png" alt="宫城良田 VS 外星人"></p>
<p><img src="https://k.sinaimg.cn/n/spider20240731/633/w2048h3385/20240731/81de-265b8e16cf5dc7f80881a5f6520de0ef.jpg/w700d1q75cms.jpg?by=cms_fixed_width" alt=""></p>
<p>今日，2024 年奥运会法国男篮 VS 日本男篮。日本球员河村永辉，以 172 的身高挑战 224 文班亚马，河村勇辉对法国狂砍29+7+6，虽败犹荣。</p>
<p>日本男篮上场的还有一个一米六七的富坚勇树，而这两个人的身高，在中国大陆连校队都难进，这是毫无疑问的。排球世界第二，足球世界杯八强常客，篮球崛起，三大球堪称亚洲之光。</p>
<p>突然想起一个故事，5 岁的小吉布森于 2014 年 10 月与爵士队签订了一份为期一天的合同，并且在球队比赛的第二节上场拿下两分。法国人戈贝尔在吉布森把球从后场带到篮下的时候抱起吉布森完成扣篮，两分有效。</p>
<p><img src="https://kslsports.com/wp-content/uploads/2023/11/4971174.jpg" alt="白血病小球迷"></p>
<p>与此同时，宇宙第一联赛 CBA？。邀请独臂的张家诚开球，国内球员没有任何互动，仅外援威姆斯摸了摸他的头。</p>
<blockquote>
<p>最新消息，USA 夺冠了，这篇周刊本该上周发布。。。</p>
</blockquote>
<p>最近开始准备找工作的事情了，如果大家有内推的话，欢迎联系我！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[使用 Biome 代替 prettier]]></title>
            <link>https://i5lin.top/blog/post/2022631263</link>
            <guid isPermaLink="false">2022631263</guid>
            <pubDate>Sun, 03 Dec 2023 17:19:25 GMT</pubDate>
            <description><![CDATA[hello 大家好~今天继续给大家带来我的新周刊内容。]]></description>
            <content:encoded><![CDATA[<img width="1029" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/84d5b7eb-f87a-4269-b21d-05ffc4f660a3"><blockquote>
<p>封面图：江苏·秋冬·红色的枫叶</p>
</blockquote>
<p>OK！继续来分享上一周接触到了一些讯息！</p>
<h3>CSS 技巧：高度自适应过度</h3>
<p>笔者在这里分享一个<code>CSS</code>高度过度动画的小技巧：在鼠标<code>hover</code>在某个元素上时，显示其子元素文本，同时添加一个动画效果。</p>
<blockquote>
<p>原文：<a href="https://dev.to/francescovetere/css-trick-transition-from-height-0-to-auto-21de?ref=dailydev"><a href="https://dev.to/francescovetere/css-trick-transition-from-height-0-to-auto-21de?ref=dailydev">🧙‍♂️ CSS trick: transition from height 0 to auto! - DEV Community</a></a></p>
</blockquote>
<p>举个例子：</p>
<pre><code class="language-html">&lt;div class=&quot;accordion&quot;&gt;
  &lt;div class=&quot;accordion-title&quot;&gt;Hover me!&lt;/div&gt;
  &lt;div class=&quot;accordion-body&quot;&gt;
    &lt;div&gt;
      &lt;p&gt;Lorem ipsum ...&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>你或许尝试过如下<code>CSS</code>样式来添加&quot;动画&quot;:</p>
<pre><code class="language-css">.accordion-body {
  height: 0;
  transition: 500ms height ease;
}

.accordion:hover .accordion-body {
  height: auto;
}
</code></pre>
<p>但是这个属性是无法实现动画的，如果你能确定容器的高度，那么可以使用<code>maax-height</code>属性来添加动画过度效果：</p>
<pre><code class="language-css">.accordion-body {
  max-height: 0;
  transition: 500ms max-height ease;
}

.accordion:hover .accordion-body {
  max-height: 200px;
}
</code></pre>
<p>最大高度的设定能触发渐变效果，鼠标<code>hover</code>的时候确实可以让高度实现渐变。</p>
<p>但是，很多时候我们无法确定内部元素的高度，最好的方案还是希望忽视其内容高度。我们可以使用<code>CSS Grid</code>属性来解决这个问题：</p>
<pre><code class="language-css">.accordion-body {
  display: grid; 
  grid-template-rows: 0fr;
  transition: 250ms grid-template-rows ease;
}

.accordion:hover .accordion-body {
  grid-template-rows: 1fr;
}

.accordion-body &gt; div {
  overflow: hidden;
}
</code></pre>
<p>Chrome 在去年这个时候发布的<code>v107</code>版本上支持了<code>grid-template-rows</code>的动画过渡支持，如果你想要在生产上使用这个特性，可能需要考虑一下兼容性。</p>
<blockquote>
<p>实在有这样的需求的话，也可以使用<code>JS</code>来实现。</p>
</blockquote>
<h3>Biome</h3>
<img width="1187" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/3078d9d8-bdc5-490f-b7f7-c4f4d711cc1d"><p>前几天<code>algora</code>发起了使用<code>Rust</code>重写<code>prettier</code>工具的挑战，最终的赢家便是<code>Biome</code>!</p>
<p><a href="https://biomejs.dev/"><a href="https://biomejs.dev/">Biome</a></a> 是一款基于<code>Rust</code>语言开发的前端工具链，完美替代<code>prettier+eslint</code>。</p>
<p>你是否觉得<code>eslint+prettier</code>处理代码的速度变得难以忍受？不妨将其切换到<code>Biome</code>上来试试。</p>
<h3>Vienna</h3>
<p><a href="https://www.vienna-rss.com/#"><a href="https://www.vienna-rss.com/#">ViennaRSS – The Free and Open Source RSS/Atom Reader for macOS</a></a>免费开源的<code>RSS</code>阅读器，我用来作为<code>Readkit</code>的平替。</p>
<h3>Comprehensive Rust</h3>
<p><a href="https://google.github.io/comprehensive-rust/index.html"><a href="https://google.github.io/comprehensive-rust/index.html">Welcome to Comprehensive Rust 🦀 - Comprehensive Rust 🦀</a></a> 这是一份来自google安卓团队的免费<code>Rust</code>开发教程。</p>
<p>推荐给具有一定编程经验且并且打算学习<code>Rust</code>的朋友。</p>
<h3>Daily.dev</h3>
<p><a href="https://app.daily.dev/"><a href="https://app.daily.dev/">daily.dev | Where developers grow together</a></a> 开发者信息聚合应用，用户可以接收官方或者社区用户分享的技术讯息，主要内容都来源于国外，喜欢学习新知识的开发者应该会很喜欢。</p>
<h3>Web3.0 资讯整理</h3>
<p><a href="https://github.com/wangschang/web3.0"><a href="https://github.com/wangschang/web3.0">wangschang/web3.0: web3.0知识整理 web3.0知识 web3.0学习资料</a></a></p>
<h3>最后</h3>
<p>笔者上周感染了流感，目前还没完全恢复，大家别放松，尤其是家里有小孩的。</p>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Javascript-ES6-函数式编程阅读随写]]></title>
            <link>https://i5lin.top/blog/post/1397540450</link>
            <guid isPermaLink="false">1397540450</guid>
            <pubDate>Wed, 05 Oct 2022 09:57:04 GMT</pubDate>
            <description><![CDATA[第一次看到这本书的时候,记得是几年前在桂电图书馆里,断断续续看了六章,后来便离开了学校.现在买了纸质书打算重新看一遍,这里记录下阅读随想.]]></description>
            <content:encoded><![CDATA[<h1>函数式编程简介</h1>
<blockquote>
<p>函数要小,要更小.</p>
</blockquote>
<p>函数式编程,远离外部环境的依赖,隔绝突变与状态.</p>
<p>数学的函数定义中,函数必须接收一个<code>参数</code>,返回一个值.函数根据接收的参数运行.给定的参数下,返回值是不变的.</p>
<p><code>JavaScript函数式编程</code>基于数学函数及其思想进行发展.运用函数式的范式进行开发能创造可缓存和可测试的代码库.</p>
<blockquote>
<p>简单分辨函数与方法 - JS,不必深究 window 和 global 对象.</p>
</blockquote>
<ul>
<li>函数: 一段通过名称可调用的代码</li>
<li>方法: 通过名称且关联对象的名称被调用的代码</li>
</ul>
<p>函数的引用透明性指的是,函数对相同的输入返回相同的值.不依赖全局数据的函数,能够自由地在多线程状态下运行,全程无锁.并且函数是<code>可缓存</code>的,合理运用这一点,可以节省大量重复计算的资源消耗和时间消耗.</p>
<blockquote>
<p>引用透明性是一种哲学</p>
</blockquote>
<p><strong>命令式?声明式?</strong></p>
<pre><code class="language-js">const arr = [1, 2, 3];
for (i = 0; i &lt; arr.length; i++) console.log(i);
</code></pre>
<p>上述代码示例,我们告诉编译器如何去做,这就是<code>命令式</code>.</p>
<pre><code class="language-js">const arr = [1, 2, 3];
arr.forEach((i) =&gt; console.log(i));
</code></pre>
<p><code>forEach</code>是一个高阶函数,意在告知编译器<code>做什么</code>,<code>如何做</code>的部分则在高阶函数内的普通函数中实现.这便是<code>声明式</code>.</p>
<p><strong>纯函数</strong>:对给定的输入,返回相同的输出的函数.纯函数不依赖于外部环境.也不会改变外部环境.易于对函数进行<code>测试</code>.</p>
<p>纯函数是<code>易于阅读</code>的.为纯函数设置<code>具有意义</code>的函数名是一种最佳实践.</p>
<blockquote>
<p>纯函数应该被设计为只做一件事,并且把它做到完美,这也会是 Unix 的哲学.</p>
</blockquote>
<p>纯函数支持<code>管道和组合</code>.</p>
<pre><code class="language-shell">cat package.json | grep axios
</code></pre>
<p><code>bash</code>命令的管道和组合威力巨大,组合是函数式编程的核心.我们称之为<code>function composition</code>.</p>
<h1>JavaScript 函数式基础</h1>
<p>略,简述基础<code>JavaScript</code>.</p>
<h1>高阶函数 HOF</h1>
<p><strong>JavaScript 数据类型</strong>:</p>
<ul>
<li>Number</li>
<li>String</li>
<li>Boolean</li>
<li>Null</li>
<li>Object</li>
<li>undefined</li>
<li>Symbol</li>
<li>BigInt</li>
</ul>
<p>除了<code>object</code>外,都是原始数据类型.</p>
<blockquote>
<p>在<a href="https://zh.wikipedia.org/wiki/%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8">计算机科学</a>中，<strong>抽象化</strong>（英语：Abstraction）是将资料与程序，以它的语义来呈现出它的外观，但是隐藏起它的实现细节。抽象化是用来减少程序的复杂度，使得程序员可以专注在处理少数重要的部分。一个电脑系统可以分割成几个<a href="https://zh.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E5%B1%A4">抽象层</a>（Abstraction layer），使得程序员可以将它们分开处理。</p>
</blockquote>
<p>抽象化,让我们专注于可控层面的工作,将复杂的内容简单化,让开发者专注于预订的目标,不必<code>事事关心</code>.</p>
<p>我们通过<code>高阶函数</code>实现抽象.</p>
<pre><code class="language-js">const forEach = (arr, Fn) =&gt; {
  for (let i = 0; i &lt; arr.length; i++) Fn(arr[i]);
};
</code></pre>
<p>这是一个简单的高阶函数,抽象了<code>遍历数组</code>的问题,如果你使用这个函数,你并不需要关注内部代码是如何实现的.上述例子是一个简单逻辑,同理也可以是更为复杂的逻辑,这样一来就将复杂问题<code>抽象</code>出来了.</p>
<p>让我们开始学习构建复杂的高阶函数.</p>
<p>创建一个<code>every</code>函数如下:</p>
<pre><code class="language-js">const every = (arr, fn) =&gt; {
  let result = true;
  for (const i = 0; i &lt; arr.length; i++) {
    result = result &amp;&amp; fn(arr[i]);
  }
  return result;
};

// for..of.. 版本
// for...of..是 es6 的函数,可以方便遍历数组
const every = (arr, fn) =&gt; {
  let result = true;
  for (const value of arr) {
    result = result &amp;&amp; fn(arr[value]);
  }
  return result;
};
</code></pre>
<p>如此一来,我们抽象了对数组遍历的操作.</p>
<p>接着,编写一个<code>some</code>函数,其接受一个数组和一个函数,如果数组中一个元素通过接收的函数返回<code>true</code>,则<code>some</code>函数返回<code>true</code>:</p>
<pre><code class="language-js">const every = (arr, fn) =&gt; {
  let result = true;
  for (const value of arr) {
    result = result || fn(arr[value]);
  }
  return result;
};
</code></pre>
<blockquote>
<p>some 函数和 every 函数都不算高效,这里只是作为对高阶函数的展示.</p>
</blockquote>
<p><code>JavaScript</code>的<code>array</code>原型内置<code>sort</code>函数,可以给数组排序.这是一个典型的<code>高阶函数</code>.它接收一个可选的函数来决定排序顺序逻辑.极大的提高了排序的灵活性.我们知道,默认的<code>sort</code>排序是将数组元素转换为<code>string</code>并且按<code>Unicode</code>编码点进行排序,因此数字 2 在默认排序算法中小于 12.让我们看看此可选的<code>compare</code>函数的骨架:</p>
<pre><code class="language-js">function compare(a, b) {
  if (条件) {
    // a 小于 b
    return -1;
  }
  if (条件) {
    // a 大于 b
    return 1;
  }
  // a 等于 b
  return 0;
}
</code></pre>
<p>举个例子,现在我们有一个用户数组,每个元素是一个用户的信息.</p>
<pre><code class="language-js">const people = [
  {
    name: &quot;Aaron&quot;,
    age: 10,
  },
  {
    name: &quot;Rose&quot;,
    age: 11,
  },
];
</code></pre>
<p>而需求则是,通过姓名排序或者通过年龄排序.根据前置知识,可以写出如下代码:</p>
<pre><code class="language-js">// 简化逻辑,忽略相等的情况
people.sort((a, b) =&gt; (a.name &gt; b.name ? 1 : -1));
people.sort((a, b) =&gt; (a.age &gt; b.age ? 1 : -1));
</code></pre>
<p>上述代码,我们将雷同的部分写了两遍.现在我们来设计一个函数,接收一个参数,返回一个函数.是的,将要设计的这个函数是一个<code>高阶函数</code>.</p>
<pre><code class="language-js">const sortBy = (property) =&gt; {
  return (a, b) =&gt; {
    return a[property] &gt; b[property] ? 1 : -1;
  };
};
// 简化版
const sortBy = (property) =&gt; (a, b) =&gt; a[property] &gt; b[property] ? 1 : -1;
</code></pre>
<p>现在,我们可以重写按<code>name</code>或者<code>age</code>的排序代码了.</p>
<pre><code class="language-js">people.sort(sortBy(&quot;name&quot;));
people.sort(sortBy(&quot;age&quot;));
</code></pre>
<p>这就是<code>高阶函数</code>的魅力.运用高阶函数,提高代码质量,降低代码数量.</p>
<h1>闭包与高阶函数</h1>
<p>简而言之,闭包就是一个内部函数.在一个函数内部的函数,可以称为<code>闭包函数</code>.</p>
<p>从<code>技术上</code>来讲,上述闭包函数的闭包场景存在三个可访问的作用域:</p>
<ul>
<li>自身声明内变量</li>
<li>外部函数变量</li>
<li>全局变量</li>
</ul>
<p><strong>闭包可以记住上下文环境</strong>.话说回来,由于我们要在函数式编程中处理很多函数,因此需要一种调试方法.</p>
<p>举个例子,一个字符串数组,想要解析成整数数组,如下代码会有问题:</p>
<pre><code class="language-js">[&quot;1&quot;, &quot;2&quot;].map(parseInt);
</code></pre>
<p><code>map</code>函数用三个参数调用了<code>parseInt</code>,分别是:</p>
<ol>
<li>element</li>
<li>index</li>
<li>arr</li>
</ol>
<p>而<code>parseInt</code>函数全盘接纳,来看看此函数的定义:</p>
<pre><code class="language-js">parseInt(string, radix);
</code></pre>
<p><code>radix</code>是可选的基数,如果提供 10,则转换为十进制的整数.</p>
<p>如果 <code>radix</code> 是 <code>undefined</code>、<code>0</code>或未指定的，JavaScript 会假定以下情况：</p>
<ol>
<li>如果输入的 <code>string</code>以 &quot;<code>0x</code>&quot;或 &quot;<code>0x</code>&quot;（一个 0，后面是小写或大写的 X）开头，那么 radix 被假定为 16，字符串的其余部分被当做十六进制数去解析。</li>
<li>如果输入的 <code>string</code>以 &quot;<code>0</code>&quot;（0）开头， <code>radix</code>被假定为<code>8</code>（八进制）或<code>10</code>（十进制）。具体选择哪一个 radix 取决于实现。ECMAScript 5 澄清了应该使用 10 (十进制)，但不是所有的浏览器都支持。<strong>因此，在使用 <code>parseInt</code> 时，一定要指定一个 radix</strong>。</li>
<li>如果输入的 <code>string</code> 以任何其他值开头， <code>radix</code> 是 <code>10</code> (十进制)。</li>
</ol>
<p>如果第一个字符不能转换为数字，<code>parseInt</code>会返回 <code>NaN</code>。</p>
<p>此时,<code>[&#39;1&#39;, &#39;2&#39;].map(parseInt)</code>的结果是: [1, NaN].</p>
<p>如何用函数式的思维,创建一个高阶函数,对<code>parseInt</code>进行抽象.</p>
<pre><code class="language-js">const unary = (fn) =&gt;
  fn.length === 1 ? fn : (arg) =&gt; fn(arg)[(&quot;1&quot;, &quot;2&quot;, &quot;3&quot;)].map(unary(parseInt));
</code></pre>
<p>现在,即使<code>map</code>以三个参数调用<code>unary</code>函数执行后返回的函数,都只会让<code>element</code>参数生效.</p>
<p>我们得到了预期中的结果:<code>[1,2,3]</code> :seedling:</p>
<p>现在,让我们为自己的工具库添加一个工具函数,这个函数接收一个函数作为参数,让这个接收的函数只能被执行一次.</p>
<pre><code class="language-js">const once = (fn) =&gt; {
  let done = false;
  return () =&gt; (done ? undefined : ((done = true), fn.apply(this, arguments)));
};
</code></pre>
<p>现在用一个变量<code>done</code>保存函数的执行状态.</p>
<pre><code class="language-js">const demoFn = (a, b) =&gt; {
  console.log(a, b, &quot;just called once.&quot;);
};
const newDemoFn = once(demoFn);
newDemoFn(1, 2); // output: 1 2 just called once.
newDemoFn(3, 4); // no output
</code></pre>
<p>继续,创建下一个函数<code>memoized</code>,使函数记住其计算结果:</p>
<pre><code class="language-js">const memoized = (fn) =&gt; {
  const lookupTable = {};
  return (arg) =&gt; lookupTable[arg] || (lookupTable[arg] = fn(arg));
};
</code></pre>
<p>一个速查<code>table</code>保存了函数解构,如果不存在则执行此函数,保存到速查表中并且返回此结果.</p>
<p><strong>memoized 函数是经典的函数式编程,是闭包与纯函数的实战</strong></p>
<h1>数组的函数式编程</h1>
<p>我们将创建一组函数用于解决常见的数组问题,关键在于函数式的方法,而非命令式的方法.</p>
<blockquote>
<p>把函数应用于一个值,并且创建新的值的过程被称为&quot;投影&quot;</p>
</blockquote>
<p><code>Array.map</code>就是典型的投影函数.我们来试着创建一个<code>map</code>函数.</p>
<pre><code class="language-js">const map = (arr, fn) =&gt; {
  const result = [];
  for (const v of arr) {
    result.push(fn(v));
  }
  return result;
};
</code></pre>
<p>一个<code>filter</code>函数,对数组内容进行过滤.</p>
<pre><code class="language-js">const filter = (arr, fn) =&gt; {
  const result = [];
  for (const v of arr) {
    if (fn(v)) result.push(v);
  }
  return result;
};
</code></pre>
<p>一个<code>reduce</code>函数,对数组的所有值进行<code>归约</code>操作.</p>
<pre><code class="language-js">var reduce = (arr, fn, defaultAccumlator) =&gt; {
  let accumlator;
  if (defaultAccumlator !== undefined) {
    accumlator = defaultAccumlator;
    for (const v of arr) {
      accumlator = fn(accumlator, v);
    }
  } else {
    accumlator = arr[0];
    for (let i = 1; i &lt; arr.length; i++) {
      accumlator = fn(accumlator, arr[i]);
    }
  }
  return [accumlator];
};
</code></pre>
<p><strong>继续</strong>,上<code>zip</code>函数,用于合并两个单独的数组,返回一个处理过的新数组.这个函数可以对给定的两个数组的限定对象进行结对处理,如何处理取决于具体的函数逻辑.结对的结果就是返回一个新的数组.</p>
<pre><code class="language-js">const zip = (arr1, arr2, fn) =&gt; {
  let index,
    result = [];
  for (index = 0; index &lt; Math.min(arr1.length, arr2.length); index++) {
    result.push(fn(arr1[index], arr2[index]));
  }
  return result;
};
</code></pre>
<h1>currying 和偏应用</h1>
<p><strong>一些术语</strong>:</p>
<ul>
<li>unary function: 一元函数,只接收一个参数的函数.</li>
<li>binary function: 二元函数</li>
<li>变参函数:接受可变数量参数的函数</li>
<li>currying: 柯里化,这是一个过程,将一个多参函数转变为一个嵌套的一元函数.</li>
</ul>
<p>上代码:</p>
<pre><code class="language-js">const curry = (binaryFn) =&gt; {
  return (firstArg) =&gt; {
    return (secondArg) =&gt; {
      return binaryFn(firstArg, secondArg);
    };
  };
};
// 简化
const curry = (binaryFn) =&gt; (firstArg) =&gt; (secondArg) =&gt;
  binaryFn(firstArg, secondArg);
</code></pre>
<p>完美利用了闭包的特性,也许对于一些深谙此道的 coder 而言,这不算什么.但是对于此刻的我来说,从未如此清晰体会到闭包和 currying.如此之美.</p>
<p>开发者编写代码的时候会在应用中编写日志,下面我们编写一个<code>日志函数</code>.</p>
<pre><code class="language-js">const loggerHelper = (mode, msg, errorMsg, lineNo) =&gt; {
  if (mode === &quot;DEBUG&quot;) {
    console.debug(msg, errorMsg + &quot;at line:&quot; + lineNo);
  } else if (mode === &quot;WARN&quot;) {
    console.warn(msg, errorMsg + &quot;at line:&quot; + lineNo);
  } else if (mode === &quot;ERROR&quot;) {
    console.error(msg, errorMsg + &quot;at line:&quot; + lineNo);
  } else {
    throw &quot;WRONG MODE&quot;;
  }
};
</code></pre>
<p>上述代码不是良好的设计,多次重用了部分代码,让整体不够简洁.之前创建的<code>curry</code>函数也无法处理这个日志函数.我们需要<strong>更进一步</strong>.我们需要把多参数函数转化为<code>unary function</code>.</p>
<pre><code class="language-js">const curry = (fn) =&gt; {
  if(typeof fn !== &#39;function&#39;) throw Error(&#39;No function provided&#39;)
  // args 是可变参数
  return function curryFn(...args) {
    // 如果可变参数少于被柯里化的函数的参数数量
    if(args.length &lt; fn.length) {
      return function() {
        // 执行此函数,返回一个处理一次参数的函数
        return curryFn.apply(null, args.concat(
        	Array.slice.call(arguments)
        ))
      }
    }
    return fn.apply(null, args)
}
</code></pre>
<blockquote>
<p><code>arguments</code>对象是所有（非箭头）函数中都可用的<strong>局部变量</strong>。你可以使用<code>arguments</code>对象在函数中引用函数的参数。此对象包含传递给函数的每个参数，第一个参数在索引 0 处。</p>
</blockquote>
<p>回到<code>日志函数</code>:</p>
<pre><code class="language-js">const errorDebugLog = curry(loggerHelper)(&quot;ERROR&quot;)(&quot;ERROR:&quot;);
const warnDebugLog = curry(loggerHelper)(&quot;WARN&quot;)(&quot;WARN:&quot;);
errorDebugLog(&quot;balabala..&quot;, 21); // output: &#39;ERROR: balabala at line 21&#39;
</code></pre>
<p>这就是闭包的魅力,在首次传入<code>loggerHelper</code>的时候,闭包生成了可访问变量作用域,记住了函数信息.</p>
<p>现在,让我们来解决两个问题:</p>
<ul>
<li>在数组内容中检查是否存在数字</li>
<li>求数组的平方</li>
</ul>
<p>首先,创建一个检查函数:<code>findNumberInArr</code></p>
<pre><code class="language-js">const match = curry((expr, str) =&gt; str.match(expr));
const hasNumber = curry(match(/[0-9]+/));
const filter = curry((fn, arr) =&gt; arr.filter(fn));
// create fn
const findNumberInArr = filter(hasNumber);
// test
findNumberInArr([&quot;demo&quot;, &quot;demo1&quot;]); // output: [&#39;demo1&#39;]
</code></pre>
<p>现在求数组的平方,我们通过<code>curry</code>函数进行处理,这次不要直接通过<code>map</code>函数传入一个平方函数解决这个问题.换一个视角.</p>
<pre><code class="language-js">const map = curry((fn, arr) =&gt; arr.map(f));
const squareAll = map((x) =&gt; x * x);
// 现在,squareAll 是一个unary 函数
squareAll([1, 2, 3]); // output: [1, 4, 9]
</code></pre>
<p>我们可以很多地方直接使用<code>squareAll</code>函数了.如果需要在多个地方求数组的平方,这个函数可以简化降低代码量,当然,我们还可以配合<code>memoized</code>函数进行缓存处理!</p>
<p>是时候谈谈<code>偏应用</code>了,接下来我们将创建一个<code>partial</code>函数以解决我们的问题.</p>
<p>比如我们有这样一个场景,我们需要在 10 秒后执行一个函数,这个需求在多个地方用得上,也许我们会直接如此编程:</p>
<pre><code class="language-js">setTimeout(() =&gt; {
  // some code
}, 10000);
</code></pre>
<p>一旦需要在另一个地方使用这个逻辑,就需要重写一遍这些代码.而且无法使用<code>curry</code>函数进行处理,因为时间参数是最后一个参数.解决这个问题的方案之一就是创建一个封装函数:</p>
<pre><code class="language-js">const setTimeoutWrapper = (time, fn) =&gt; {
  setTimeout(fn, time);
};
</code></pre>
<p>然后使用<code>curry</code>函数进行优化.然而,我们可以进一步减少创建此类函数的开销.</p>
<p><strong>这就是偏应用</strong>的应用场景.</p>
<pre><code class="language-js">var partial = (fn, ...partialArgs) =&gt; {
  let args = partialArgs
  return (...fullArguments) {
    let arg = 0
    for(let i=0;i&lt;args.length &amp;&amp; arg &lt; fullArguments.length;i++) {
      if(args[i] === undefined) {
        args[i] = fullArguments[arg++]
      }
    }
    return fn.apply(null, args)
  }
}

const delayTenMs = partial(setTimeout, undefined, 10)
const demoFn = () =&gt; {
  // sdasdsadad
}
delayTenMs(demoFn)
</code></pre>
<p>再次使用了闭包,记住了<code>setTimeout</code>需要的参数列表长度,暂未提供的参数用 undefined 代替.后续返回一个函数,提供参数的同时,补全之前用 undefined 代替的部分参数,最终执行函数.</p>
<p>例如,我们需要美化一个<code>json</code>对象的输出.先看看 json stringify 函数的定义.</p>
<pre><code class="language-js">JSON.stringify(value[, replacer [, space]])
</code></pre>
<p><strong>参数</strong></p>
<ul>
<li><p><code>value</code></p>
<p>将要序列化成 一个 JSON 字符串的值。</p>
</li>
<li><p><code>replacer</code> 可选</p>
<p>如果该参数是一个函数，则在序列化过程中，被序列化的值的每个属性都会经过该函数的转换和处理；如果该参数是一个数组，则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中；如果该参数为 null 或者未提供，则对象所有的属性都会被序列化。</p>
</li>
<li><p><code>space</code> 可选</p>
<p>指定缩进用的空白字符串，用于美化输出（pretty-print）；如果参数是个数字，它代表有多少的空格；上限为 10。该值若小于 1，则意味着没有空格；如果该参数为字符串（当字符串长度超过 10 个字母，取其前 10 个字母），该字符串将被作为空格；如果该参数没有提供（或者为 null），将没有空格。</p>
</li>
</ul>
<pre><code class="language-js">const obj = { foo: &quot;bar&quot;, bar: &quot;foo&quot; };
// 第二参数
JSON.stringify(obj, null, 2);
// 想办法移除样板代码 null 和 2
const prettyJson = partial(JSON.stringify, undefined, null, 2);
prettyJson(obj);
</code></pre>
<p>啊!<code>partial</code>函数有 bug.<code>partialArgs</code>传递的是数组,数组传递的是引用.如果不创建一个新的数组,并且在最后重置数组的话,执行一次<code>partial</code>化后的函数<code>delayTenMs</code>,内部 args 就固定了,闭包变量保存了首次执行的时候提供的参数去替换<code>undefined</code>,这里的<code>undefined</code>就像占位符.</p>
<p>书上并没有给出一个示例代码来解决问题,只是提出存在 bug 的观点,我写下了如下代码,使用扩展运算符重置占位符效果的<code>undefined</code>变量.</p>
<pre><code class="language-js">const partial = (fn, ...partialArgs) =&gt; {
  let args = [...partialArgs];
  return (...fullArgs) =&gt; {
    let argIndex = 0;
    for (
      let index = 0;
      index &lt; args.length &amp;&amp; argIndex &lt; fullArgs.length;
      index++
    ) {
      if (args[index] === undefined) {
        args[index] = fullArgs[argIndex++];
      }
    }
    const result = fn.apply(null, args);
    args = [...partialArgs];
    return result;
  };
};
</code></pre>
<p>晚安.</p>
<p>2020 年 12 月 21 日 01:41:11</p>
<h1>管道和组合</h1>
<p>函数式组合在函数式编程中被称为<code>组合 composition</code>.</p>
<blockquote>
<p>每个程序只为做好一件事,重构要比在旧的复杂程序中添加新属性更好. From Ken·Thompson</p>
</blockquote>
<blockquote>
<p>每一个程序的输出应该是另一个未知程序的输入.</p>
</blockquote>
<p>我们将要创建<code>compose</code>函数,例如:</p>
<pre><code class="language-js">const compose = (a, b) =&gt; (c) =&gt; a(b(c));
</code></pre>
<p>依然是熟悉的高阶函数,接收函数作为参数,返回函数.返回的函数的<code>参数</code>是关键.</p>
<p><code>compose</code>函数能解决我们常见的一些问题.你是否写过类似如下代码:</p>
<pre><code class="language-js">const data = someFn(&quot;this&quot;);
const result = otherFn(data);
</code></pre>
<p>一个函数的输出,作为另一个函数的输入.<code>compose</code>函数为此而生.</p>
<pre><code class="language-js">const getResult = compose(otherFn, someFn);
</code></pre>
<p><code>getResult</code>是一个函数,接收的参数跟<code>someFn</code>函数一致.我们创建了一个函数<code>getResult</code>.这是一种优雅而简单的实现方式.</p>
<p>上述<code>someFn</code>只接收一个参数,如果需要接收多个参数的话,我们可以使用<code>curry</code>和<code>partial</code>两个函数进行优化.</p>
<blockquote>
<p>组合的思想就是把小函数组合成大函数,简单的函数易于阅读,测试和维护.</p>
</blockquote>
<p>创建小的函数单元,可以通过 compose 组合重建应对各种需求.</p>
<p>但是,上述<code>compose</code>函数无法处理更多函数作为参数.仅仅支持两个函数作为参数是不够的,让我们来优化一下.</p>
<pre><code class="language-js">const compose =
  (...fns) =&gt;
  (value) =&gt;
    reduce(fns.reverse(), (acc, fn) =&gt; fn(acc), value);
</code></pre>
<p>竟然如此简单桥面,接收一个入口参数<code>value</code>,首先设置为初始的<code>acc</code>值,依次执行并且返回作为下一个函数的入口.</p>
<p>组合是从右到左执行的,而管道则从左到右.接下来创建<code>pipe</code>管道函数.</p>
<pre><code class="language-js">const pipe =
  (...fns) =&gt;
  (value) =&gt;
    reduce(fns, (acc, fn) =&gt; fn(acc), value);
</code></pre>
<p>只是执行方向不同而已,因此参数列表不必反向.</p>
<blockquote>
<p>在公共代码库中建议使用单一的数据流,我更喜欢管道,因为这让我想起 shell 知识.同时使用管道和组合容易在团队中引起混淆.</p>
</blockquote>
<p>接下来,本书描述了<strong>组合</strong>的优势.</p>
<p>组合支持结合律.书上并没有详细的介绍和案例分析.只是单纯举例:</p>
<pre><code class="language-js">compose(f, compose(g, h)) == compose(compose(f, g), h);
</code></pre>
<p>通过组合小函数的方式,让函数的组合更加灵活.</p>
<p>下面,我们来创建一个<code>identity</code>函数,用于分析调试.接收一个参数,打印并且返回.</p>
<pre><code class="language-js">const identity = (it) =&gt; {
  console.log(it);
  return it;
};
</code></pre>
<p>由于组合和管道数据是流动的关系,可以在其间插入<code>identity</code>函数,输出数据用于调试.这确实非常简单有效.</p>
<h1>函子</h1>
<p>本章最重要的内容就是<code>编程错误处理</code>.我们需要了解一个新的概念:<code>functor:函子</code>,它将以纯函数的形式帮我们处理错误.</p>
<blockquote>
<p>函子是一个普通对象,它实现了<code>map</code>函数,在遍历每一个对象的时候生成一个新的对象.</p>
</blockquote>
<p>函子是<code>容器</code>,其持有值.</p>
<pre><code class="language-js">const Container = function (val) {
  this.value = val;
};
</code></pre>
<blockquote>
<p>不使用箭头函数,箭头函数没有<code>[[construct]]</code>和<code>prototype</code>属性,无法用<code>new</code>实例化.</p>
</blockquote>
<p>现在,<code>container</code>可以持有传给它的任何值.</p>
<pre><code class="language-js">let a = new Container(3)
=&gt; Container(value: 3)
let bObj = new Container({a: 1})
=&gt; Container({a:1})
</code></pre>
<p>继续,创建一个<code>of</code>静态方法.</p>
<pre><code class="language-js">Container.of = function (value) {
  return new Container(value);
};
</code></pre>
<p>于是,我们可以使用<code>static</code>函数创建对象了.</p>
<pre><code class="language-js">const testObj = Container.of(3)
=&gt; Container(value:3)
</code></pre>
<p>接下来,我们需要创建<code>map</code>函数,之后这便是一个<code>函子</code>.</p>
<p><img src="https://i.loli.net/2020/12/24/vX1AIKxhgOlQzMd.png" alt=""></p>
<p>看代码:</p>
<pre><code class="language-js">Container.prototype.map = function (fn) {
  return Container.of(fn(this.value));
};
</code></pre>
<p>经过<code>map</code>函数处理,会返回一个新的 container 对象.于是,我们可以进行如下编码:</p>
<pre><code class="language-js">let double = x =&gt; x + x
Container.of(3).map(double)
=&gt; Container(6)
Container.of(3).map(double).map(double)
=&gt; Container(12)
</code></pre>
<p>得到了我个人一直以来不够了解的链式调用的实现思路.</p>
<blockquote>
<p>函子是一个实现了 map 契约的对象</p>
</blockquote>
<p>所以,函子能用在什么地方?</p>
<pre><code class="language-js">const MayBe = function (val) {
  this.value = val;
};
MayBe.of = function (v) {
  return new MayBe(v);
};
MayBe.prototype.isNothing = function () {
  return this.value === null || this.value === undefined;
};
MayBe.prototype.map = function (fn) {
  return this.isNothing() ? MayBe.of(null) : MayBe.of(this.value);
};
</code></pre>
<p>:construction_worker:</p>
<p>上用例</p>
<pre><code class="language-js">MayBe.of(&#39;string&#39;).map(x =&gt; x.toUpperCase())
=&gt; MayBe {value: &#39;STRING&#39;}
</code></pre>
<p>即使不是<code>typescript</code>,无法避免<code>of</code>方法传入<code>null</code>或者<code>undefined</code>,内部的<code>map</code>方法也会做一次检查,逻辑抽象出来避免了错误.代码没有在<code>null</code>或者<code>undefined</code>下崩溃.这是一种声明式的方式去编程,这也是一个安全的容器.</p>
<pre><code class="language-js">MayBe.of(&quot;pg&quot;)
  .map((x) =&gt; x.toUpperCase())
  .map((x) =&gt; `Mr. ${x}`);
</code></pre>
<p>链式调用,优美简洁.</p>
<p><strong>另一个函子</strong><code>Either</code>,上述<code>MayBe</code>函子在传入<code>null</code>或者<code>undefined</code>后,最后的结果是<code>null</code>.可是我们很难分析出问题出在哪里.我们需要一个更强大的函子,解决分支拓展的问题.</p>
<pre><code class="language-js">const Nothing = function (val) {
  this.value = val;
};
Nothing.of = function (v) {
  return new Nothing(v);
};
Nothing.prototype.map = function (f) {
  return this;
};
const Some = function (val) {
  this.value = val;
};
Some.of = function (v) {
  return new Some(v);
};
Some.prototype.map = function (f) {
  return Some.of(f(this.value));
};
</code></pre>
<p><code>Nothing</code>的 map 函数,返回自身,而不是运行函数<code>f</code>.</p>
<p>2020 年 12 月 25 日 12:57:10 :100:</p>
<p>能在<code>Some</code>上运行函数,而<code>Nothing</code>不行.来吧,实现<code>Either</code>.</p>
<pre><code class="language-js">const Either = {
  Some,
  Nothing,
};
</code></pre>
<p>如果我们有一个<code>web</code>请求,返回数据可能是正常数据,或者一条错误信息.</p>
<pre><code class="language-js">const getData = (type) =&gt; {
  let resp;
  try {
    resp = Some.of(data);
  } catch {
    resp = Nothing.of(error);
  }
};
</code></pre>
<p>后续返回的 response 对象依然是函子,可以使用链式调用的 map 函数.但是,错误信息能保存下去,<code>Nothing</code>从头到尾都不会变,直接返回<code>this</code>使得后续的 map 函数<code>失效</code>.</p>
<p>现在,你根据上下文,可以很容易看出异常出在哪里.是的,是<code>catch</code>到了错误.</p>
<p>上述函子都是<code>pointed</code>函子,ES6 的 Array.of 也是<code>pointed</code>函子.</p>
<h1>深入理解 Monad 函子</h1>
<p><code>reddit</code>开放了一些<code>api</code>接口,例如:<strong><a href="https://www.reddit.com/search.json?q=something">https://www.reddit.com/search.json?q=something</a></strong></p>
<p>粘贴一个随机的返回数据,不必详细深究:</p>
<pre><code class="language-json">{
  &quot;kind&quot;: &quot;Listing&quot;,
  &quot;data&quot;: {
    &quot;after&quot;: &quot;t3_k1qcrx&quot;,
    &quot;dist&quot;: 25,
    &quot;facets&quot;: {}.
    &quot;modhash&quot;: &quot;&quot;,
    &quot;children&quot;: [...],
  &quot;before&quot;: null
  }
}
</code></pre>
<p><code>children</code>部分非常庞大,不再展开.每一个<code>children</code>都是一个对象,内含一个<code>Permalink</code>键,值是一个相对<code>url</code>,访问这个<code>url</code>则能获取到评论数组.</p>
<p>我们如何按搜索的结果,获取文章的评论,最后返回一个数组,每个元素是一个对象,对象内是<code>title</code>和<code>comments</code>.</p>
<pre><code class="language-js">const request = require(&quot;sync-request&quot;);
const searchReddit = (search) =&gt; {
  let response;
  try {
    response = JSON.parse(
      request(
        &quot;GET&quot;,
        &quot;https://www.reddit.com/saerch.json?q=&quot; + encodeURI(search)
      ).getBody(&quot;utf8&quot;)
    );
  } catch (err) {
    response = { msg: &quot;something wrong&quot;, errorCode: err[&quot;statusCode&quot;] };
  }
  return response;
};

const getComments = (link) =&gt; {
  let resp;
  try {
    resp = JSON.parse(
      request(&quot;GET&quot;, &quot;https://www.reddit.com/&quot; + link).getBody(&quot;utf8&quot;)
    );
  } catch (err) {
    resp = {
      msg: &quot;get comment failed&quot;,
      errorCode: err[&quot;statusCode&quot;],
    };
  }
  return resp;
};

// 合并两个函数
const mergeViaMayBe = (searchText) =&gt; {
  let redditMayBe = MayBe.of(searchReddit(searchText));
  let ans = redditMayBe
    .map((arr) =&gt; arr[&quot;data&quot;])
    .map((arr) =&gt; arr[&quot;children&quot;])
    .map((arr) =&gt;
      map(arr, (x) =&gt; {
        return {
          title: x[&quot;data&quot;].title,
          permalink: x[&quot;data&quot;].permalink,
        };
      })
    )
    .map((obj) =&gt;
      map(obj, (x) =&gt; {
        return {
          title: x.title,
          comments: MayBe.of(
            getComments(x.permalink.replace(&quot;?ref=search_posts&quot;, &quot;.json&quot;))
          ),
        };
      })
    );
};
</code></pre>
<p>运用函子,可以使用链式方法解决问题,非常优雅.</p>
<p>但是,这依然不够,观察上述结果中<code>comments</code>依然是<code>MayBe</code>对象,想要真正取值还是需要继续<code>map</code>调用.</p>
<p>下面,介绍<code>Monad</code>函子以解决过多的<code>map</code>嵌套问题.让<code>MayBe</code>武装到牙齿.</p>
<pre><code class="language-js">MayBe.prototype.join = function () {
  return this.isNothing() ? MayBe.of(null) : this.value
}
let joinExample = MayBe.of(MayBe.of(1))
=&gt; MayBe{value: MayBe{value: 1}}
joinExample.join()
// 返回值展开一个层级
=&gt; MayBe {value: 1}
</code></pre>
<p>如果我们想要对内部的值进行操作,也许可以先展开层级,再执行<code>map</code>函数,以减少<code>map</code>的调用.那么既然当我们需要展开一层的时候都需要在后面跟一个<code>join</code>方法,我们可以再封装一个<code>chain</code>函数来做这件事.</p>
<pre><code class="language-js">MayBe.prototype.chain = function (f) {
  return this.map(f).join();
};
</code></pre>
<p><code>Monad</code>就是包含<code>chain</code>方法的特殊函子.或者说,一个函子拥有<code>chain</code>方法,就可以称为<code>Monad</code></p>
<p>2020 年 12 月 26 日 01:34:57</p>
<h1>使用 Generator</h1>
<p>如果你是<code>promise</code>的粉丝,建议学习<code>Generator</code>及其解决异步代码问题的方式.</p>
<p>让我们先来谈谈<code>同步和异步</code>:</p>
<ul>
<li><p>同步:函数执行的时候阻塞调用者,直到函数执行结束返回结果.</p>
</li>
<li><p>异步:函数执行不会阻塞,但是函数执行结束就会返回结果.</p>
</li>
</ul>
<p>让我们来创建<code>Generator</code>,注意观察这个特殊语法.</p>
<pre><code class="language-js">function* gen() {
  return &quot;first generator&quot;;
}
</code></pre>
<p>我们在函数前面用一个星号 <strong>*</strong> 来表示这是一个<code>Generator</code>函数.</p>
<pre><code class="language-js">let genResult = gen();
</code></pre>
<p>此刻,<code>genResult</code>并不是<code>first generator</code>,而是:</p>
<p><code>gen {[[GeneratorStatus]]: &quot;suspended&quot;, [[GeneratorRecevier]]: Window}</code></p>
<p>这是一个<code>Generator</code>原始类型的实例.这个实例拥有<code>next</code>方法.执行此方法,上面的代码返回一个对象:</p>
<p><code>{value: &#39;first generator&#39;, done: true}</code></p>
<p><code>Generator</code>实例如同序列,一旦使用<code>next</code>消费,则不能再次得到上次消费的值.也就是说,如果你继续执行<code>next()</code>方法,结果的<code>vaue</code>将是<code>undefined</code>.</p>
<pre><code class="language-js">function* gen() {
  yield 1;
  yield 2;
  yield 3;
}
</code></pre>
<p>有趣的是,实例执行<code>next</code>将会返回<code>yield</code>后的结果,并且下次执行<code>next</code>会从当前<code>yield</code>后继续执行.当所有<code>yield</code>结果都消费之后,<code>done</code>属性变成<code>true</code>.</p>
<pre><code class="language-js">let genResult = gen();
for (let v of genResult()) {
  console.log(v);
}
// 1
// 2
// 3
</code></pre>
<p><code>for..of..</code>利用了<code>next</code>和<code>done</code>属性完成遍历.</p>
<p><code>Generator</code>不止如此,还可以想其实例通过<code>next</code>函数传递数据.</p>
<pre><code class="language-js">function * sayFullName(){
  let firstName = yield
  let secondName = yield
  console.log(`${firstName} - ${secondName}`)
}

// call it
let fullName = sayFullName()
fullName.next()
fullName.next(&#39;Fname&#39;)
fullName.next(&#39;Sname&#39;)
=&gt; console log : Fname - Sname
</code></pre>
<p>首次运行<code>next</code>,函数暂停于: <code>let firstName = yield</code></p>
<p>再次运行带参数的<code>next</code>,则<code>let firstName = yield</code>转为<code>let firstName = &quot;Fname&quot;</code></p>
<blockquote>
<p><code>Generator</code>与<code>异步应用</code>可以很和谐.</p>
</blockquote>
<p>假如有两个本质上是异步的函数如下:</p>
<pre><code class="language-js">let getDataOne = (cb) =&gt; {
  setTimeout(() =&gt; {
    cb(&quot;dummy data one&quot;);
  }, 1000);
};

let getDataTwo = (cb) =&gt; {
  setTimeout(() =&gt; {
    cb(&quot;dummy data two&quot;);
  }, 1000);
};
</code></pre>
<p>一旦时间过去,就执行传入的回调函数<code>cb</code>.如果要用<code>generator</code>来解决回调函数可能导致的函数回调地狱的问题?</p>
<p>让我们来改造<code>getDataOne</code>:</p>
<pre><code class="language-js">let geneator;
let getDataOne = () =&gt; {
  setTimeout(() =&gt; {
    // 调用 Generator
    // 使用 next 加参数传送数据
    generator.next(&quot;fake data one&quot;);
  }, 1000);
};
</code></pre>
<p>然而,我并不是很理解这段代码,<code>generator</code>是<code>undefined</code>,为何调用<code>next</code>.</p>
<p>2020 年 12 月 26 日 22:56:50</p>
<p>2020 年即将结束,在异步编程的问题上让我们拥抱<code>async</code>和<code>await</code>吧.</p>
<p>终于看完了这本书,这本书非常适合我现在的水平,让我能够学习得很愉快.再次感谢,即使昨天是圣诞节我依然一个人过.</p>
<p>再会啦.</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[CSS之Grid布局]]></title>
            <link>https://i5lin.top/blog/post/1397542082</link>
            <guid isPermaLink="false">1397542082</guid>
            <pubDate>Wed, 05 Oct 2022 09:58:26 GMT</pubDate>
            <description><![CDATA[善用网格布局，可以轻松实现界面布局，并且具有现代浏览器良好的支持度。]]></description>
            <content:encoded><![CDATA[<p>网格布局<code>(Grid)</code>被视为当前最强大的<code>CSS</code>布局方案。</p>
<p><img src="https://images.unsplash.com/photo-1671725501844-1e6d0081bf64?ixlib=rb-4.0.3&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3540&q=80" alt=""></p>
<h2>1. 前言</h2>
<p>我需要的布局大致如下：</p>
<p><img src="https://i.loli.net/2021/06/10/GlXYwLiM4uyUIst.png" alt=""></p>
<p>可以看到，此布局并不复杂，我们可以使用多种方式来实现它，但今天我的计划是使用<code>Grid</code>布局。</p>
<p><code>Grid</code>布局将容器划分为“行”与“列”，产生单元格，然后指定“item”所在的单元格，因此也常被视为“二维布局”。</p>
<h2>2. 基本概念</h2>
<h3>2.1 容器和项目</h3>
<p>如名所示，最外层的元素作为容器<code>(container)</code>，内部每一个最外层的元素作为一个单独的项目<code>(item)</code>。</p>
<pre><code class="language-html">&lt;main&gt;
  &lt;section&gt;a&lt;/section&gt;
  &lt;section&gt;b&lt;/section&gt;
  &lt;section&gt;c&lt;/section&gt;
&lt;/main&gt;
</code></pre>
<p><code>Grid</code>布局针对<code>main</code>生效，<code>section</code>作为<code>item</code>，其内部元素与布局无关。</p>
<h3>2.2 行和列</h3>
<p><img src="https://i.loli.net/2021/06/10/IFci7q2pKmdLsG6.png" alt=""></p>
<p>我想这一张图已经非常明显地使用深色体现出行<code>(row)</code>与列<code>(column)</code>的区别，行和列是有交叉的。</p>
<h3>2.3 单元格和网格线</h3>
<p>行列交叉的区域，我们称之为<code>Cell(单元格)</code>，如前言所示，我们将子元素放在单元格中。而深色区域，我们将之称为<code>Grid line(网格线)</code>，通常<code>n</code>行<code>m</code>列，即可产生可供布局的<code>n*m</code>个<code>Cell</code>。</p>
<p>不要讲空白区域视为单元格，单元格始终是<code>行</code>和<code>列</code>相交产生的。</p>
<h2>3. 容器属性和项目属性</h2>
<p><code>Grid</code>布局属性分为定义在<code>container</code>上的<code>容器属性</code>,定义在<code>item</code>上的<code>项目属性</code>。</p>
<h3>3.1 容器属性</h3>
<h4>3.1.1 display</h4>
<p>显示为<code>container</code>设置<code>display: grid</code>显示属性布局为<code>grid</code>。</p>
<pre><code class="language-css">div {
  display: grid;
}
</code></pre>
<p>此时，<code>container</code>是一个单独的容器，默认是块级元素，也可以设置<code>display: inline-grid</code>为行内<code>Grid</code>布局，使其整体视为一个行内块级元素。</p>
<blockquote>
<p>网格布局将使得子项（item）的<code>float</code>、<code>display: inline-block</code>、<code>display: table-cell</code>、<code>display: vertical-align</code>、<code>display: column-*</code>等设置失效。</p>
</blockquote>
<h4>3.1.2 grid-template-rows 、grid-template-columns</h4>
<p><code>grid</code>布局除了需要显示指定布局类型为<code>grid</code>外，还需要指定行和列的值。</p>
<p><code>grid-template-rows</code>定义行高，有多少行就提供多少个值。</p>
<p><code>grid-template-column</code>定义列宽，同样，有多少列就提供多少个值。</p>
<p>例如，如果我们要设置一个九宫格，则分别需要三行三列：</p>
<pre><code class="language-css">.container {
  display: grid;
  grid-template-rows: 100px 100px 100px;
  grid-template-column: 100px 100px 100px;
}
</code></pre>
<p>如此一来配上<code>item</code>(css 提供一些颜色值):</p>
<pre><code class="language-html">&lt;div id=&quot;container&quot;&gt;
  &lt;div class=&quot;item item-1&quot;&gt;1&lt;/div&gt;
  &lt;div class=&quot;item item-2&quot;&gt;2&lt;/div&gt;
  &lt;div class=&quot;item item-3&quot;&gt;3&lt;/div&gt;
  &lt;div class=&quot;item item-4&quot;&gt;4&lt;/div&gt;
  &lt;div class=&quot;item item-5&quot;&gt;5&lt;/div&gt;
  &lt;div class=&quot;item item-6&quot;&gt;6&lt;/div&gt;
  &lt;div class=&quot;item item-7&quot;&gt;7&lt;/div&gt;
  &lt;div class=&quot;item item-8&quot;&gt;8&lt;/div&gt;
  &lt;div class=&quot;item item-9&quot;&gt;9&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>我们可以得到一个九宫格布局：</p>
<p><img src="https://i.loli.net/2021/06/10/ACZfnmovTdDsiwk.png" alt=""></p>
<p>除了使用<code>px</code>这样的绝对单位，也可以使用百分数，甚至可以使用<code>repeat</code>类函数简化赋值：</p>
<pre><code class="language-css">.container {
  display: grid;
  grid-template-columns: repeat(3, 33.33%);
  grid-template-rows: repeat(3, 33.33%);
}
</code></pre>
<p>甚至是：</p>
<pre><code class="language-css">.container {
  display: grid;
  grid-template-columns: repeat(2, 100px 40px 50px);
  grid-template-rows: 50px 50px 50px;
}
</code></pre>
<p>定义了<code>100px 20px 80px 100px 20px 80px</code>，6 列宽度不一的列。</p>
<p><img src="https://i.loli.net/2021/06/10/1UQYPNlAax5rodI.png" alt="image-20210610012604962"></p>
<p>如上所示，第三行由于没有<code>item</code>，默认空白。</p>
<p>某些场合下，我们希望容器尽可能填充每一行的<code>item</code>，可以使用<code>auto-fill</code>关键字：</p>
<pre><code class="language-css">.container {
  display: grid;
  grid-template-columns: repeat(auto-fill, 100px);
}
</code></pre>
<p><img src="https://i.loli.net/2021/06/10/RkMNLhud9IpliCw.png" alt="image-20210610012923160"></p>
<p>容器根据最大宽度进行自动列填充，此时行与列的数量是根据宽度变化的。</p>
<p>某些场合下，我们希望动态根据片段比例对行数进行判断，次数可以使用<code>fr(fraction)</code>关键字，表示列的宽度片段，例如：</p>
<pre><code class="language-css">.container {
  display: grid;
  grid-template-columns: 2fr 1fr;
}
</code></pre>
<p>上述示例表示，第一列宽度为整个容器宽度的<code>2/3</code>，第二列为<code>1/3</code>，一般配合绝对宽度使用可以实现很灵活的布局效果：</p>
<pre><code class="language-css">.container {
  display: grid;
  grid-template-columns: 150px 1fr 2fr;
}
</code></pre>
<p>上述示例，每一行先扣除第一列的<code>150px</code>宽度，剩下的再动态计算分配。也可以使用<code>auto</code>关键字，由浏览器决定长度。</p>
<pre><code class="language-css">grid-template-columns: 100px auto 100px;
</code></pre>
<p>网格线可以具有名字，并且可以有多个名字<code>（使用中括号括起来）</code>，方便后续复用。</p>
<pre><code class="language-css">.container {
  display: grid;
  grid-template-columns: [c1] 100px [c2] 100px [c3] auto [c4];
  grid-template-rows: [r1] 100px [r2] 100px [r3] auto [r4];
}
</code></pre>
<h4>3.1.3 gap</h4>
<p>网格线间距属性<code>gap</code>，其属性为行和列的简写：</p>
<pre><code class="language-css">.container {
  gap: &lt;row-gap&gt; &lt;column-gap&gt;;
  gap: 20px 20px;
}
</code></pre>
<p>如果简写忽略了第二个值，则默认等于第一个值。</p>
<h4>3.1.4 grip-template-areas</h4>
<p>网格布局可以通过字符串，抽象画的划分不同<code>item</code>所属的区域。</p>
<pre><code class="language-css">.container {
  display: grid;
  grid-template-columns: 100px 100px 100px;
  grid-template-rows: 100px 100px 100px;
  grid-template-areas:
    &quot;a b c&quot;
    &quot;d e f&quot;
    &quot;g h i&quot;;
}

.area-a {
  grid-area: a;
  ....;
}
</code></pre>
<p><code>grid-template-areas</code>通过空格将不同区域分割开来，然后可以在<code>css</code>中直接使用<code>grid-area</code>属性和区域名作为值，再为标签添加类即可针对性的设置样式。</p>
<p>不使用的区域可以使用<code>.</code>占位，可以不同<code>cell</code>具有相同的<code>area</code>名，以便于指定样式，例如：</p>
<pre><code class="language-css">grid-template-areas:
  &quot;a . a&quot;
  &quot;b . b&quot;
  &quot;c . d&quot;;
</code></pre>
<blockquote>
<p><code>grip-template-rows</code>可以定义子项高度，同时也可以为网格线命名，而网格线可以有多个名字。<code>grid-template-areas</code>指定区域名的时候，也默认生成了<code>areaName-start</code>和<code>areaName-end</code>这样的网格线别名。</p>
</blockquote>
<h4>3.1.5 grid-auto-flow</h4>
<p>容器划分好网格后，容器内<code>item</code>按顺序放置，默认先行后列，这个顺序是可以更改的。<code>grid-auto-flow</code>就是设置这个顺序的属性，默认值为<code>row</code>,先行后列，如果需要先列后行，则设置值为<code>column</code>。</p>
<p>当某行或者某列按次序放置子项的时候，存在剩余宽度不足的情形，如果需要可以在<code>row</code>或<code>column</code>后添加一个<code>dense</code>值，二者用空格分开，意为尽可能让子项连续密集显示，如此一来就会跳过宽度超过剩余宽度的子项，按序优先使用后续满足条件的子项。例如：</p>
<p><img src="https://i.imgur.com/P3bSu2L.png" alt=""></p>
<p>属性：</p>
<pre><code class="language-css">grid-auto-flow: row dense;
</code></pre>
<p>结果：</p>
<p><img src="https://raw.githubusercontent.com/youyiqin/markdown_imgs/master/bg2019032514-20210612221449095.png" alt=""></p>
<p>对于某些严格需要避免中间空白的布局来说，这个属性非常有效。</p>
<h4>3.1.6 单元格位置</h4>
<p><code>justify-items</code>设置单元格水平布局，<code>align-items</code>设置单元格垂直布局，二者可选的值为：</p>
<ul>
<li>start</li>
<li>end</li>
<li>center</li>
<li>Stretch （默认值，拉伸）</li>
</ul>
<p>如果要设置整个容器内的单元格位置，也就是将容器内所有单元格视为一个整体，其布局属性可用：</p>
<ul>
<li>justify-content:：整体水平对齐</li>
<li>align-content： 整体垂直对齐</li>
<li>place-content：此为上述两个属性的简写方式，如果忽略第二个值则采用第一个值</li>
</ul>
<p>这几个布局属性的值类似<code>flex</code>的布局值，分别是：</p>
<ul>
<li>start</li>
<li>end</li>
<li>center</li>
<li>stretch（拉伸）</li>
<li>Space-around 项目两侧间隔相等，子项之间距离两个间隔</li>
<li>Space-between 子项之间距离相等，第一个子项和最后一个子项左边或右边没有空白，紧贴容器</li>
<li>Space-evenly 子项左右空白距离相等</li>
</ul>
<h4>3.1.7 grid-auto-rows and grid-auto-columns</h4>
<p>当<code>容器网格</code>只有三行的时候，如果需要指定某个<code>子项</code>在第五行，这时候浏览器自动<code>根据子项大小</code>创建新的网格以放置额外的子项，我们可以通过<code>grid-auto-rows</code>和<code>grid-auto-columns</code>指定自动创建的网格的高度和宽度。</p>
<p>例如：</p>
<pre><code class="language-css">.container {
  display: grid;
  grid-template-columns: 100px 100px 100px;
  grid-template-rows: 100px 100px 100px;
  grid-auto-rows: 50px;
}
</code></pre>
<p><img src="https://raw.githubusercontent.com/youyiqin/markdown_imgs/master/bg2019032525.png" alt=""></p>
<p>之所以<code>8</code>和<code>9</code>会占据图中的位置，是因为我们使用<code>css</code>指定其行和列的位置值：</p>
<pre><code class="language-css">.item-8 {
  background-color: #d0e4a9;
  grid-row-start: 4;
  grid-column-start: 2;
}

.item-9 {
  background-color: #4dc7ec;
  grid-row-start: 5;
  grid-column-start: 3;
}
</code></pre>
<p>由此引出<code>grid-row-start</code>和<code>grid-column-start</code>属性，可以指定其元素的位置。</p>
<p>除了<code>start</code>还有<code>end</code>可以指定，看示例：</p>
<pre><code class="language-css">.item-1 {
  grid-column-start: 2;
  grid-column-end: 4;
}
</code></pre>
<p>此时如果没有指定<code>grid-auto-flow: row dense;</code>,则会让布局看起来如下图所示：</p>
<p><img src="https://raw.githubusercontent.com/youyiqin/markdown_imgs/master/bg2019032526.png" alt=""></p>
<p>为了方便记忆，可以将网格线数字改为网格线名。</p>
<p>这四个属性的值还可以使用<code>span</code>关键字，表示&quot;跨越&quot;，即左右边框（上下边框）之间跨越多少个网格。</p>
<pre><code class="language-css">.item-1 {
  grid-column-start: span 2;
}
</code></pre>
<p><img src="https://raw.githubusercontent.com/youyiqin/markdown_imgs/master/bg2019032528.png" alt=""></p>
<h4>3.1.8 属性简写</h4>
<p>此前我有翻译过<code>google html &amp; css guide</code>文档风格指南，其中有一条建议是尽量在<code>css</code>中使用简写，我认为这是一个很好的准则。</p>
<p><code>grid-template</code>属性是<code>grid-template-columns</code>、<code>grid-template-rows</code>和<code>grid-template-areas</code>这三个属性的合并简写形式。</p>
<p><code>grid</code>属性是<code>grid-template-rows</code>、<code>grid-template-columns</code>、<code>grid-template-areas</code>、 <code>grid-auto-rows</code>、<code>grid-auto-columns</code>、<code>grid-auto-flow</code>这六个属性的合并简写形式。</p>
<blockquote>
<p>如果你喜欢简写，务必不要弄错简写的属性顺序。</p>
</blockquote>
<p><code>grid-column</code>属性是<code>grid-column-start</code>和<code>grid-column-end</code>的合并简写形式，<code>grid-row</code>属性是<code>grid-row-start</code>属性和<code>grid-row-end</code>的合并简写形式。</p>
<pre><code class="language-css">.item {
  grid-column: &lt;start-line&gt; / &lt;end-line&gt;;
  grid-row: &lt;start-line&gt; / &lt;end-line&gt;;
}
</code></pre>
<p>下面是一个例子。</p>
<pre><code class="language-css">.item-1 {
  grid-column: 1 / 3;
  grid-row: 1 / 2;
}
/* 等同于 */
.item-1 {
  grid-column-start: 1;
  grid-column-end: 3;
  grid-row-start: 1;
  grid-row-end: 2;
}
</code></pre>
<p>上面代码中，项目<code>item-1</code>占据第一行，从第一根列线到第三根列线。</p>
<p>这两个属性之中，也可以使用<code>span</code>关键字，表示跨越多少个网格。</p>
<pre><code class="language-css">.item-1 {
  background: #b03532;
  grid-column: 1 / 3;
  grid-row: 1 / 3;
}
/* 等同于 */
.item-1 {
  background: #b03532;
  grid-column: 1 / span 2;
  grid-row: 1 / span 2;
}
</code></pre>
<p><a href="https://jsbin.com/volugow/edit?html,css,output">上面代码</a>中，项目<code>item-1</code>占据的区域，包括第一行 + 第二行、第一列 + 第二列。</p>
<p><img src="https://raw.githubusercontent.com/youyiqin/markdown_imgs/master/bg2019032529.png" alt=""></p>
<p><code>grid-area</code>属性还可用作<code>grid-row-start</code>、<code>grid-column-start</code>、<code>grid-row-end</code>、<code>grid-column-end</code>的合并简写形式，直接指定项目的位置。</p>
<pre><code class="language-css">.item {
  grid-area: &lt;row-start&gt; / &lt;column-start&gt; / &lt;row-end&gt; / &lt;column-end&gt;;
}

.item-1 {
  grid-area: 1 / 1 / 3 / 3;
}
</code></pre>
<p>效果如上图所示。</p>
<h3>3.2 子项属性</h3>
<p>子项和容器的属性可以拆分开来，通过诸如<code>justify-self</code>等带<code>self</code>关键字的属性控制单独的子项的样式，并且优先级高于容器上相关的样式属性。</p>
<h4>3.2.1 justify-self 、align-self、place-self</h4>
<p><code>justify-self</code>属性设置单元格内容的水平位置（左中右），跟<code>justify-items</code>属性的用法完全一致，但只作用于单个项目。</p>
<p><code>align-self</code>属性设置单元格内容的垂直位置（上中下），跟<code>align-items</code>属性的用法完全一致，也是只作用于单个项目。</p>
<pre><code class="language-css">.item {
  justify-self: start | end | center | stretch;
  align-self: start | end | center | stretch;
}
</code></pre>
<p><code>place-self</code>属性是<code>align-self</code>属性和<code>justify-self</code>属性的合并简写形式。</p>
<pre><code class="language-css">place-self: center center;
</code></pre>
<p>如果省略第二个值，<code>place-self</code>属性会认为这两个值相等。</p>
<h2>4. 解题</h2>
<p>学习了<code>grid</code>布局的知识后，让我们将之运用到一开头我的需求中来，再次看这个图：</p>
<p><img src="https://raw.githubusercontent.com/youyiqin/markdown_imgs/master/GlXYwLiM4uyUIst-20210613022942072.png" alt=""></p>
<p>针对性的容器<code>CSS</code>如下：</p>
<pre><code class="language-css">.container {
}
</code></pre>
<h2>参考</h2>
<ul>
<li><a href="http://www.ruanyifeng.com/blog/2019/03/grid-layout-tutorial.html">CSS Grid 网格布局教程 - 阮一峰的网络日志</a></li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[ECMAScript 2022新规范]]></title>
            <link>https://i5lin.top/blog/post/1397544441</link>
            <guid isPermaLink="false">1397544441</guid>
            <pubDate>Wed, 05 Oct 2022 10:00:26 GMT</pubDate>
            <description><![CDATA[一年一度的ECMAScript规范发布，看看今年有什么新内容！

## 前言

`ECMAScript` 是标准化的 `JavaScript` 语言，于 1997 年发布了第一版，现已发展成为世界上使用最广泛的通用编程语言之一。

`ECMAScript 2022 Language`是 ECMAScript 语言规范的第 13 版，因此我们也可以称之为`ES13`!

那么，今年新]]></description>
            <content:encoded><![CDATA[<p>一年一度的ECMAScript规范发布，看看今年有什么新内容！</p>
<h2>前言</h2>
<p><code>ECMAScript</code> 是标准化的 <code>JavaScript</code> 语言，于 1997 年发布了第一版，现已发展成为世界上使用最广泛的通用编程语言之一。</p>
<p><code>ECMAScript 2022 Language</code>是 ECMAScript 语言规范的第 13 版，因此我们也可以称之为<code>ES13</code>!</p>
<p>那么，今年新增了哪些内容呢？</p>
<h3>类增强</h3>
<p>简单来说有以下三点：</p>
<ul>
<li>属性可以通过以下方式创建<ul>
<li>公共的实例字段</li>
<li>静态的公共字段</li>
</ul>
</li>
<li>新增私属性插槽（井号）<ul>
<li>私有字段（私有实例字段和私有静态字段）</li>
<li>私有方法和访问器（非静态）</li>
</ul>
</li>
<li>静态的初始化块</li>
</ul>
<p>直接看代码：</p>
<pre><code class="language-js">class MyClass {
  instancePublicField = 1; // 公共实例字段
  static staticPublicField = 2; // 静态公共字段

  #instancePrivateField = 3; // 私有实例字段
  static #staticPrivateField = 4; // 静态私有字段

  #nonStaticPrivateMethod() {}
  get #nonStaticPrivateAccessor() {} // 非静态私有访问器
  set #nonStaticPrivateAccessor(value) {}

  static #staticPrivateMethod() {} // 私有静态方法
  static get #staticPrivateAccessor() {} // 静态访问器
  static set #staticPrivateAccessor(value) {}

  static {
    // 静态的初始化块
  }

	constructor(value) {
    this.property = value; // 公共实例字段
  }
}
</code></pre>
<p>举一个静态初始化块的例子：</p>
<pre><code class="language-js">class Translator {
  static translations = {
    yes: &#39;ja&#39;,
    no: &#39;nein&#39;,
    maybe: &#39;vielleicht&#39;,
  };
  static englishWords = [];
  static germanWords = [];
  static { // (A)
    for (const [english, german] of Object.entries(this.translations)) {
      this.englishWords.push(english);
      this.germanWords.push(german);
    }
  }
}
</code></pre>
<p>通过使用静态初始化块，我们可以将所有类相关的代码放在类的内部。</p>
<p>上述例子中，可以将初始化两个<code>word</code>数组的过程优化到静态初始化块内部，从而实现我们需要的效果，如果需要还可以访问私有插槽属性。</p>
<p>需要注意的是：</p>
<ul>
<li>一个类可以有多个静态初始化块</li>
<li>静态初始化块和静态属性可以交错初始化</li>
<li>超类（superclass）的静态初始化块在子类的静态初始化块之前执行</li>
</ul>
<p>此外，类的私有化属性插槽可以通过<code>in</code>操作符检查，举个例子：</p>
<pre><code class="language-js">class ClassWithPrivateSlot {
  #privateSlot = true;
  static hasPrivateSlot(obj) {
    return #privateSlot in obj;
  }
}

const obj1 = new ClassWithPrivateSlot();
assert.equal(
  ClassWithPrivateSlot.hasPrivateSlot(obj1), true
);

const obj2 = {};
assert.equal(
  ClassWithPrivateSlot.hasPrivateSlot(obj2), false
);
</code></pre>
<h3>顶层 await</h3>
<p>规范新增顶层<code>await</code>支持，现在我们可以直接在模块内使用顶层<code>await</code>，而不必再额外进入异步函数内再使用<code>await</code>。</p>
<p>看实例：</p>
<pre><code class="language-js">const res = await fetch(&quot;https://jsonplaceholder.typicode.com/todos/1&quot;).then(
  (response) =&gt; response.json()
);

console.log(&quot;res is:&quot;, res);
</code></pre>
<p>保存上述内容为文件<code>index.mjs</code>，使用<code>node v17.5</code>以上版本或<code>bunjs</code>直接运行：</p>
<p><code>node index.mjs</code>即可直接获取到输出。</p>
<p>那么，我们可以在什么地方使用这个特性呢？举个例子：</p>
<pre><code class="language-js">// first.mjs
const response = await fetch(&#39;http://example.com/first.txt&#39;);
export const first = await response.text();

// main.mjs
import {first} from &#39;./first.mjs&#39;;
import {second} from &#39;./second.mjs&#39;;
assert.equal(first, &#39;First!&#39;); // true
assert.equal(second, &#39;Second!&#39;); // true
</code></pre>
<p>有了顶层<code>await</code>，我们可以像使用同步导出那样直接导出异步的值。</p>
<p>大致等同于以下代码：</p>
<p><code>first.mjs</code>:</p>
<pre><code>export let first;
export const promise = (async () =&gt; { // (A)
  const response = await fetch(&#39;http://example.com/first.txt&#39;);
  first = await response.text();
})();
</code></pre>
<p><code>main.mjs</code>:</p>
<pre><code>import {promise as firstPromise, first} from &#39;./first.mjs&#39;;
import {promise as secondPromise, second} from &#39;./second.mjs&#39;;
export const promise = (async () =&gt; { // (B)
  await Promise.all([firstPromise, secondPromise]); // (C)
  assert.equal(first, &#39;First content!&#39;);
  assert.equal(second, &#39;Second content!&#39;);
})();
</code></pre>
<p>方便了不少，不是吗？</p>
<h3>error.cause</h3>
<p>为<code>Error</code>及其子类添加<code>cause</code>属性，更方便地传递错误原因，增强传递错误信息的能力。</p>
<pre><code class="language-js">function readFiles(filePaths) {
  return filePaths.map(
    (filePath) =&gt; {
      try {
        // ···
      } catch (error) {
        throw new Error(
          `While processing ${filePath}`,
          {cause: error}
        );
      }
    });
}
</code></pre>
<p>现在，可以访问错误对象的<code>cause</code>属性获取更多详情。</p>
<h3>.at()</h3>
<p>就像使用<code>python</code>一样使用<code>JavaScript</code>，通过下标访问数组变得容易，具有显式的函数支持，此外跟直接使用<code>[]</code>访问不一样的是，<code>.at()</code>支持传负数参数进行逆序取值。</p>
<h3>正则表达式Flag：/d</h3>
<p>看例子：</p>
<pre><code class="language-js">const matchObj = /(a+)(b+)/d.exec(&#39;aaaabb&#39;);

assert.equal(
  matchObj[1], &#39;aaaa&#39;
);
assert.deepEqual(
  matchObj.indices[1], [0, 4] // (A)
);

assert.equal(
  matchObj[2], &#39;bb&#39;
);
assert.deepEqual(
  matchObj.indices[2], [4, 6] // (B)
);
</code></pre>
<p>通过<code>d</code>执行的正则表达式匹配，可以得到匹配的元素的起始下标。</p>
<h3>Object.hasOwn(obj, propKey)</h3>
<p><code>Object.hasOwn(obj, propKey)</code>提供了一种安全的方式通过属性<code>key</code>检查对象是否含有非继承的目标属性。</p>
<pre><code class="language-js">const proto = {
  protoProp: &#39;protoProp&#39;,
};
const obj = {
  __proto__: proto,
  objProp: &#39;objProp&#39;,
}

assert.equal(&#39;protoProp&#39; in obj, true); // (A)

assert.equal(Object.hasOwn(obj, &#39;protoProp&#39;), false); // (B)
assert.equal(Object.hasOwn(proto, &#39;protoProp&#39;), true); // (C)
</code></pre>
<h2>最后</h2>
<p>众所周知：</p>
<p><code>JavaScript</code>和<code>ECMAScript</code>的区别在于，前者是一个不同平台实现的编程语言，后者是这门语言的标准规范，平台基于此规范实现这门语言。</p>
<p><code>ECMAScript</code>由<code>TC39</code>标准委员会设计，其成员来自各大科技公司和其他平台等等，每年都会在年中发布最新的规范。</p>
<p>不同的提案经过四个阶段，最终才会进入规范，在进入规范之前不建议使用，因为其始终是不稳定的，关注每年的新增内容即可。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[HTML浅解]]></title>
            <link>https://i5lin.top/blog/post/1397545704</link>
            <guid isPermaLink="false">1397545704</guid>
            <pubDate>Wed, 05 Oct 2022 10:01:26 GMT</pubDate>
            <description><![CDATA[​ 作为一个 web 开发者，我们需要掌握的 HTML 知识到底应该有多少？HTML 真的如此简单吗，我们是否应该给与其更多的重视？]]></description>
            <content:encoded><![CDATA[<p>​ 带着这些疑问，我决定重新学习<code>HTML</code>知识，如何学习？本文将带着疑问去学习<code>HTML</code>相关的知识，并且做出一定的总结。</p>
<h2>前言</h2>
<p><strong>HTML</strong>（超文本标记语言），也是万维网的核心标记语言，对于现代浏览器来说，<code>HTML</code>已经发展到了第五个版本，在多年的演变和改进之下，许多不合时宜的内容被清除了，同时随着版本更迭也有新的内容添加进来，作为一个 web 开发者，我们需要紧跟技术的发展，保持前瞻性和技术敏感度。</p>
<p>知识无限，时间有限。</p>
<p>忽略掉那些琐碎的片段，我们将从不同的问题开启每一个知识点。</p>
<h2>Q&amp;A</h2>
<h3>1. HTML 和 XML 语法的差别</h3>
<p>HTML（超文本标记语言）和 XML（可扩展标记语言）结构类似，但是在语法上具有以下不同之处：</p>
<ul>
<li>XML 严格区分大小写</li>
<li>XML 具有严格的树状结构，禁止省略结束标记</li>
<li>XML 属性值必须用引号包裹起来，而在 HTML 中则是可选的</li>
<li>XML 所有属性必须具有值，HTML 则允许无值属性（采用默认值）</li>
<li>XML 解析器不会像 HTML 这样过滤空格</li>
<li>XML 没有固定的标记标签，所有标签都是自定义可扩展的</li>
</ul>
<p>二者在作用上也不同：</p>
<ul>
<li>XML 偏向于保存数据，可以被视为持久化结构</li>
<li>HTML 偏向于描述数据结构</li>
</ul>
<p>其他方面：</p>
<ul>
<li>在浏览器中，HTML 文件的媒体类型是<code>text/html</code>，而 XML 的媒体类型则是<code>application/xhtml+xml</code>，不同的<code>MIME</code>类型在浏览器中将会以不同的解析器去解析文档。</li>
</ul>
<h3>2. 简单快速介绍一下 HTML 的知识</h3>
<p>从最简单的一份<code>html</code>文档说起：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;title&gt;Sample page&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;Sample page&lt;/h1&gt;
    &lt;br /&gt;
    &lt;p&gt;This is a &lt;a href=&quot;demo.html&quot;&gt;simple&lt;/a&gt; sample.&lt;/p&gt;
    &lt;!-- this is a comment --&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>如上述示例那样，HTML 文档具有树状结构，每一个节点标签都具备<code>开始标签</code>，但是不一定具有结束标签，标签支持<code>嵌套</code>。</p>
<p>每个标签都可能有属性和值，举个例子：</p>
<pre><code class="language-html">&lt;a href=&quot;url&quot;&gt;somewhere&lt;/a&gt;
</code></pre>
<p>标签具有各自的意义，属性值总是在<code>开始标签</code>内，并且如果属性值不包含特殊字符，则可以省略引号，但是更推荐保留引号，让整体结构的描述更准确。</p>
<p>浏览器通过自己的<code>HTML</code>解析器去解析<code>HTML</code>文档，并且将之转换为<code>DOM（文档对象模型）</code>，这种模型将保存在内存中。</p>
<p><img src="https://i.loli.net/2021/05/24/5e1l7FsB24hyqtR.png" alt="image-20210524205054501"></p>
<p>上图是上述简单文档的<code>DOM</code>树状图形式，<code>DOM</code>提供了诸多<code>API</code>可以让开发者控制和修改<code>DOM</code>的结构。</p>
<blockquote>
<p>我们可以通过：<code>caniuse.com</code>对标签和属性的兼容性进行查询</p>
</blockquote>
<p>又绕回来说上述代码，最基础的上述结构中，可以继续延伸了解一下大部分标签。</p>
<ul>
<li><code>&lt;html&gt;</code> 为根元素，页面唯一</li>
<li><code>&lt;head&gt;</code> 为头部信息标签，页面唯一，内部常嵌套一些补充信息和标题<ul>
<li><code>&lt;meta&gt;</code> 元数据，常用于设置字符集，添加相关名字和描述性内容，常用于提高<code>SEO</code></li>
<li><code>&lt;script&gt;</code> 引入 JavaScript</li>
<li><code>&lt;title&gt;</code> 标题设置</li>
<li><code>&lt;style&gt;</code> 嵌入 Css</li>
<li><code>&lt;link&gt;</code> 外部资源链接</li>
<li><code>&lt;base&gt;</code> 定义页面默认超链接的默认地址和打开方式，建议放在 head 的最前面</li>
</ul>
</li>
<li><code>&lt;body&gt;</code> 页面主体，页面唯一，常用标签全部嵌套在内部。<ul>
<li><code>&lt;h1&gt;~&lt;h6&gt;</code> 设置标题</li>
<li><code>&lt;p&gt;</code> 设置段落文本</li>
<li><code>&lt;a&gt;</code> 超链接</li>
<li><code>&lt;pre&gt;</code>预格式化</li>
<li><code>&lt;q&gt;,&lt;blockquete&gt;</code>长短引用内容</li>
<li><code>&lt;br /&gt;</code>,<code>&lt; hr /&gt;</code> 换行标签和水平线标签</li>
<li><code>&lt;b&gt;</code> 粗体</li>
<li><code>&lt;small&gt;</code>小号字体</li>
<li><code>&lt;i&gt;</code>斜体</li>
<li><code>&lt;rt&gt;</code> 中文发音注释，顶部显示</li>
<li><code>&lt;sub&gt;</code>下标</li>
<li><code>&lt;sup&gt;</code> 上标</li>
<li><code>&lt;iframe&gt;</code>内联框架，替换<code>&lt;frame&gt;</code></li>
<li><code>&lt;cite&gt;</code>标签定义作品（比如书籍、歌曲、电影、电视节目、绘画、雕塑等等）的标题。默认斜体，引用分离，有助于自动摘录参考的功能。</li>
<li><code>&lt;div&gt;</code> 通用块元素</li>
<li><code>&lt;span&gt;</code> 内联文本元素</li>
<li><code>&lt;textarea&gt;</code> 输入区</li>
<li><code>&lt;input&gt;</code> 输入框<ul>
<li><code>&lt;datalist&gt;</code> 输入框可选值列表</li>
</ul>
</li>
<li><code>&lt;img&gt;</code> 图像</li>
<li><code>&lt;map&gt;</code> 图像区域映射<ul>
<li><code>&lt;area&gt;</code> 定义区域位置和映射目标地址</li>
</ul>
</li>
<li><code>&lt;figure&gt;</code>标记文档中的媒体内容<ul>
<li><code>&lt;figcaption&gt;</code>媒体的标题，常用于媒体标签的上面或者下面</li>
</ul>
</li>
<li><code>&lt;button&gt;</code> 按钮</li>
<li><code>&lt;from&gt;</code> 表单<ul>
<li><code>&lt;fieldset&gt;</code> 表单边框<ul>
<li><code>&lt;legend&gt;</code>表单边框描述，内容标题</li>
</ul>
</li>
</ul>
</li>
<li><code>&lt;video&gt;</code> 视频</li>
<li><code>&lt;audio&gt;</code> 音频<ul>
<li><code>&lt;source&gt;</code> 数据源和媒体类型说明，添加多个备用</li>
</ul>
</li>
<li><code>&lt;table&gt;</code> 表格<ul>
<li><code>&lt;col&gt;</code> 配合 <code>&lt;colgroup&gt;</code>为列添加属性</li>
<li><code>&lt;caption&gt;</code> 表格标题</li>
<li><code>&lt;tbody&gt;</code> 表格主体</li>
<li><code>&lt;td&gt;</code>表格单元</li>
<li><code>&lt;th&gt;</code> 表头单元格</li>
<li><code>&lt;tfoot&gt;</code>表格脚注</li>
<li>...</li>
</ul>
</li>
<li><code>&lt;address&gt;</code> 定义作者地址信息</li>
<li><code>&lt;ul&gt;, &lt;ol&gt;</code> 有序和无序列表<ul>
<li><code>&lt;li&gt;</code> 列表项</li>
</ul>
</li>
<li><code>&lt;dl&gt;</code> 定义列表，增加列表的灵活性<ul>
<li><code>&lt;dt&gt;</code> 定义列表标题</li>
<li><code>&lt;dd&gt;</code> 定义描述，通常在标题下方，并且具有缩进</li>
</ul>
</li>
<li><code>&lt;del&gt;</code> 被删除的文本</li>
<li><code>&lt;ins&gt;</code> 默认下划线，定义插入的行内文本</li>
<li><code>&lt;details&gt;</code> 默认不展开的内容隐藏<ul>
<li><code>&lt;summary&gt;</code> details 默认显示的描述信息，不支持<code>IE</code></li>
</ul>
</li>
<li><code>&lt;header&gt;</code> 正文中的标题</li>
<li><code>&lt;nav&gt;</code>旨在封装一组链接，常用于导航栏</li>
<li><code>&lt;footer&gt;</code> 页脚内容</li>
<li><code>&lt;main&gt;</code> 正文主体核心内容区域</li>
<li><code>&lt;article&gt;</code> 文章容器</li>
<li><code>&lt;section&gt;</code> 相关性内容，比如章节、页眉、页脚或文档中的其他部分。</li>
<li><code>&lt;aside&gt;</code> 表示与它周围文本没有密切关系的内容，通常的广告区域、搜索、分享链接。</li>
<li><code>&lt;canvas&gt;</code> canvas 图像容器</li>
<li><code>&lt;embed&gt;</code> 嵌入页面的元素，外部应用，互动插件等等，本意是不属于当前页面的内容，使用时指定外部资源类型。</li>
<li><code>&lt;diakig&gt;</code> 对话框，支持性很差</li>
<li><code>&lt;mark&gt;</code> 类似<code>strong</code>，H5 属性且更为通用</li>
<li><code>&lt;meter&gt;</code>给定的数据范围度量，需要制定相关属性</li>
<li><code>&lt;output&gt;</code>表示输出结果的行内元素</li>
<li><code>&lt;time&gt;</code>标注时间</li>
<li><code>&lt;datetime&gt;</code>标注日期</li>
<li><code>&lt;progress&gt; </code>进度条</li>
<li><code>&lt;select&gt;</code> 下拉列表<ul>
<li><code>&lt;optgroup&gt;</code> 可选组选项<ul>
<li><code>&lt;option&gt;</code> 选项</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>所有<code>HTML</code>短语标签如下：</p>
<table>
<thead>
<tr>
<th><em></th>
<th>呈现为被强调的文本。</th>
</tr>
</thead>
<tbody><tr>
<td><strong></td>
<td>定义重要的文本。</td>
</tr>
<tr>
<td><dfn></td>
<td>定义一个定义项目。</td>
</tr>
<tr>
<td><code></td>
<td>定义计算机代码文本。</td>
</tr>
<tr>
<td><samp></td>
<td>定义样本文本。</td>
</tr>
<tr>
<td><kbd></td>
<td>定义键盘文本。它表示文本是从键盘上键入的。它经常用在与计算机相关的文档或手册中。</td>
</tr>
<tr>
<td><var></td>
<td>定义变量。您可以将此标签与 <code>&lt;pre&gt;</code> 及 <code>&lt;code&gt;</code> 标签配合使用。</td>
</tr>
</tbody></table>
<h3>3. HTML 和语义化有何意义</h3>
<p>我们知道，<code>HTML</code>标签都具有独特的语义，使用合适的标签来组织整体的结构却不是开发者“必须”去做的一件事，在很多情况下，开发者混用不适宜的标签去达到相同的效果屡见不鲜。</p>
<p>清晰的语义能带来良好的页面结构，并且非常有利于搜索引擎和网络爬虫解析页面内容，大大提高页面内容的识别准确性，优化<code>SEO</code>让页面得到更好的传播和搜索权重。</p>
<h3>4. 谈谈 HTML 中的语法错误问题</h3>
<p>HTML 的语法错误的处理措施非常宽松，某种程度上 HTML 语法的灵活性让错误的语法产生了不完整的行为。</p>
<p><code>HTML</code>的语法错误在浏览器中是可以被允许的，不良的语法结构将导致 DOM 语法树的结构不够直观。</p>
<p>来看看如下语法结构：</p>
<pre><code class="language-html">&lt;p&gt;
  &lt;i
    &gt;She dreamt.
    &lt;p&gt;
      &lt;i
        &gt;She dreamt that she ate breakfast.
        &lt;p&gt;
          &lt;i
            &gt;Then lunch.
            &lt;p&gt;&lt;i&gt;And finally dinner.&lt;/i&gt;&lt;/p&gt;&lt;/i
          &gt;
        &lt;/p&gt;&lt;/i
      &gt;
    &lt;/p&gt;&lt;/i
  &gt;
&lt;/p&gt;
</code></pre>
<p>浏览器通过解析器解析此结构最终在 DOM 语法树中的结果如下：</p>
<p><img src="https://i.loli.net/2021/05/26/bfOFlSEG73CZqLn.png" alt="image-20210526223813242"></p>
<p>也许这与我们的期望相去甚远，原来两层的语法树却形成了多层的嵌套结构，我们可以从中看出几个语法错误的特点：</p>
<ul>
<li>HTML 的语法非常宽松，语法错误是可以被允许和正常解析的</li>
<li>错误的语法将在解析后产生意想不到的 DOM 结构，并且很有可能降低性能</li>
</ul>
<h3>5. 简单谈谈你对于 HTML 元标签的理解</h3>
<p><code>HTML</code>文档信息可以在其<code>head</code>标签内使用<code>meta</code>标签进行补充说明，一般的形式是以<code>key/value</code>键值对为结构描述关于文档的额外说明。</p>
<p><code>&lt;meta&gt;</code>不支持嵌套，没有闭合标签，其内容不会再页面显示，通常我们会增加一些额外信息描述，这往往有利于搜索引擎和提高<code>SEO</code>效率，对于机器是可读的</p>
<p><code>meta</code>的有效属性包含以下四个：</p>
<ul>
<li><code>name</code> 属性名</li>
<li><code>content</code>属性值</li>
<li><code>scheme</code> 指定<code>content</code>的格式，但<code>H5</code>不支持</li>
<li><code>http-equiv</code> 把<code>content</code>关联到<code>HTTP</code>头部，例如控制页面刷新时间，设置字符集类型和文档类型</li>
<li><code>charset</code> 字符集</li>
</ul>
<p>以下是我们日常工作中可能会用到的键值对数据，。</p>
<pre><code class="language-html">&lt;head&gt;
  &lt;meta name=&quot;description&quot; content=&quot;博客&quot; /&gt;
  &lt;meta name=&quot;keywords&quot; content=&quot;HTML,CSS,XML,JavaScript&quot; /&gt;
  &lt;meta name=&quot;author&quot; content=&quot;someone&quot; /&gt;
  &lt;meta name=&quot;revised&quot; content=&quot;Tutorialspoint, 5/27/2021&quot; /&gt;
  &lt;meta charset=&quot;UTF-8&quot; /&gt;
  &lt;meta http-equiv=&quot;refresh&quot; content=&quot;5; url = https://www.google.com&quot; /&gt;
&lt;/head&gt;
</code></pre>
<h3>6. HTML 无障碍方面有何了解</h3>
<p>无障碍设计的目标是让健全或者残疾人，年轻人或老年人都可以平等，便捷地获取站点的服务，增加受益群体。</p>
<p>通常，最大的受益人群是不擅长访问互联网的普通人或者视力障碍用户，甚至是听力、精神、肢体障碍用户。</p>
<p><code>W3C</code>推动了无障碍化实施规范，主要的辅助即使包括硬件放大镜或者软件放大镜，盲文显示器和读屏软件等。</p>
<p>在开发的时候，可以采用可读性高的设计方案，甚至是对比度高的字体和图片等。另外，编写语义化的<code>HTML</code>将会对屏幕阅读器非常友好。</p>
<p>在时间充分的情况下，建议遵循<code>WAI-ARIA</code>规范或者<a href="http://www-03.ibm.com/able/guidelines/web/accessweb.html">IBM 无障碍化网站开发检查项(IBM Web accessibility checklist – Version 5.2 )</a>进行开发。</p>
<p>在测试的时候，可以使用<code>webking</code>静态检测工具进行测试。</p>
<h3>7. 关于 HTML 注释的知识点</h3>
<p>对于任何语言来说，注释都是很有必要的，在浏览器中注释将会被忽略，但是对于复杂的页面来说，必要的注释能让阅读源码的人提高阅读效率。</p>
<p><code>HTML</code>使用<code>&lt;!-- ... --&gt;</code>作为注释标签（注意，边界不能有多余的空格，否则注释将被视为普通字符串），注释可以跨行。</p>
<p>还有一种注释被称为条件注释：<code>&lt;!--[if IE 8]&gt; ... &lt;![endif]--&gt;</code>，这种注释在<code>IE</code>中能被有效识别，在其他浏览器中被忽略，因此尝尝被用于为<code>IE</code>浏览器设置兼容性属性。</p>
<blockquote>
<p><code>&lt;comment&gt;</code>标签内部也可以被视为注释（IE 浏览器有效），但是在<code>HTML5</code>中已经无效</p>
</blockquote>
<p>在一些旧浏览器中，依然支持如下两种注释：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Commenting Style Sheets&lt;/title&gt;
    &lt;style&gt;
      &lt;!-- .example {
        border: 1px solid #4a7d49;
      }
      //--&gt;
    &lt;/style&gt;
    &lt;script&gt;
      &lt;!--
      document.write(&quot;Hello World!&quot;);
      //--&gt;
    &lt;/script&gt;
  &lt;/head&gt;

  &lt;body&gt;
    &lt;div class=&quot;example&quot;&gt;Hello , World!&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>但是在现代浏览器中，上述<code>JS</code>和<code>CSS</code>部分注释都无效。</p>
<h3>8. 对于 img 标签的理解</h3>
<p><code>img</code>标签必须设置<code>src</code>和<code>alt</code>属性，添加具有实际意义的<code>alt</code>属性可以让页面更友好。</p>
<p><code>img</code>是替换内联元素，可以理解为<code>inline-block</code>。</p>
<p>可以提前为<code>img</code>标签添加<code>width</code>和<code>height</code>属性值和对齐方向属性<code>align</code>(H5 已弃用)，如此一来在加载过程中也能具有稳定的宽高显示，从而保证页面的稳定。如果要实现固定的宽高比，自适应宽高比，则可以预设宽高值之一为固定值，另一个为<code>auto</code>。</p>
<blockquote>
<p><code>img</code>此类自闭合标签是无法在内部嵌套子元素的，因此也就没法使用伪类<code>::before</code>和<code>::after</code></p>
</blockquote>
<p><code>img</code>标签的事件监听如下：</p>
<ul>
<li><code>onload</code>: 图像顺利加载完成</li>
<li><code>onerror</code>: 图像资源 404、403、500、请求超时或者返回的资源不是有效的图片</li>
<li><code>onabort</code>: 图像加载被强制停止，例如主动点击浏览器<code>stop</code>按钮</li>
</ul>
<p><code>HTML5</code>的趋势之一，就是减少元素标签的属性值，关于样式的部分尽可能使用<code>CSS</code>文件来描述。</p>
<p>另外，<code>img</code>支持<code>usemap</code>属性，配合<code>map</code>标签内嵌<code>area</code>可以实现点击图片不同区域跳转到不同的目标区域或者地址的功能。</p>
<h3>9. 列举几个 HTML 最佳实践</h3>
<ul>
<li>编写有效可读的<code>DOM</code><ul>
<li>全部小写</li>
<li>保持缩进</li>
<li>自动关闭标签</li>
<li>避免过渡注释</li>
<li>组织<code>DOM</code>，尽量减少元素</li>
</ul>
</li>
<li>尽量不使用内联样式和内联脚本，内联的样式必须是关键样式（渲染页面顶部所需的最小 CSS 集）</li>
<li>将脚本标签放在 body 底部</li>
<li>照顾无障碍用户，使用意义明确的标签和描述性属性值</li>
<li>正确使用 title 和 meta 标签，增强<code>SEO</code></li>
<li>压缩文件和使用<code>CDN</code>加速</li>
<li>对数据交互进行验证，永远不要相信用户的输入，提高应用的安全性</li>
</ul>
<h3>10. 谈谈你所理解的 table 标签</h3>
<p>曾经开发者们使用表格进行布局，现在<code>table</code>标签更纯粹了，绝大多数用来展示表格数据。</p>
<p>表格允许开发者们将图片、图像、链接等数据排列到单元格的行和列中。</p>
<p>一个表格标签，内部嵌套着行盒子<code>tr</code>(table row)和单元格<code>td</code>。如果有必要，可以添加一行<code>th</code>表格列标题。</p>
<p>甚至是使用<code>thead</code>、<code>tbody</code>、<code>tfoot</code>将整体结构拆开：</p>
<pre><code class="language-html">&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Month&lt;/th&gt;
      &lt;th&gt;Savings&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;January&lt;/td&gt;
      &lt;td&gt;$100&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;February&lt;/td&gt;
      &lt;td&gt;$80&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
  &lt;tfoot&gt;
    &lt;tr&gt;
      &lt;td&gt;Sum&lt;/td&gt;
      &lt;td&gt;$180&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tfoot&gt;
&lt;/table&gt;
</code></pre>
<p>表格标签支持多属性控制样式，但是推荐使用<code>CSS</code>控制样式。</p>
<h3>11. 关于列表</h3>
<p>列表分为三种：</p>
<ul>
<li>有序列表 <code>ol</code></li>
<li>无序列表<code>ul</code></li>
<li>自定义列表 <code>dl</code></li>
</ul>
<p>而非自定义列表项则统一使用<code>li</code>,列表可以使用类型属性<code>type</code>和排序起始属性<code>start</code>控制列表项前面的标志。</p>
<p>自定义列表则使用<code>dt</code>作为列表标题，<code>dd</code>作为列表项，且默认存在缩进。</p>
<p>如果需要对列表前的类型样式做修改，则可以使用<code>list-style-type</code>设置浏览器默认支持的几个元素。</p>
<p>如果需要自定义样式，则可以配合伪类<code>::marker</code>使用。</p>
<p>甚至只要是<code>display: list-item</code>且可内嵌子元素的标签，都支持左边自定义一个显示顺序内容，合理使用<code>counter()</code>可以为多个项设置计算后的顺序前缀内容，这对于创建菜单序号和级联序号非常有用。</p>
<h3>12. 超链接</h3>
<p>超链接可以在用户点击链接后进行当前页面跳转或打开新的标签页进行跳转，为用户提供不同站点之间的导航功能，开发者可以通过<code>a</code>标签创建超链接，点击超链接后的行为取决于 <code>a</code>标签的<code>target</code>属性值。</p>
<p>可以为<code>target</code>设置以下几个不同属性值：</p>
<ul>
<li><code>_blank</code>：在新的浏览器标签页打开链接的地址</li>
<li><code>_self</code>：在相同的<code>frame</code>内打开链接地址（默认值）</li>
<li><code>_parent</code>：在父级<code>frame</code>打开链接地址</li>
<li><code>_top</code>：在当前标签顶层<code>body</code>下打开链接地址（chrome 将会提示离开当前页面）</li>
<li><code>targetframe</code>：在目标<code>frame</code>内打开链接地址</li>
</ul>
<p>除了作为导航外，还可以实现下载功能：</p>
<pre><code class="language-html">&lt;a href=&quot;large.jpg&quot; download&gt;下载&lt;/a&gt;
</code></pre>
<blockquote>
<p>IE 不支持<code>dowload</code>属性</p>
</blockquote>
<p>提供一个<code>download</code>属性让浏览器处理下载功能，并且可以为<code>download</code>属性提供值作为下载的文件名。</p>
<p>但是此方案在跨域场景下各厂家实现区别很大，因此不同源的方案不如使用<code>download.js</code>这个第三方库。</p>
<p>另外，我们在使用邮件类型的地址的时候，需要为<code>href</code>属性值之前添加协议类型：<code>mailto:</code>，例如：</p>
<pre><code class="language-html">&lt;a href=&quot;mailto: abc@example.com&quot;&gt;Send Email&lt;/a&gt;
</code></pre>
<p>浏览器对此链接的点击行为做出反应，调用默认的邮件处理程序预置目标邮件地址，并且可以在<code>mail</code>地址后添加参数，邮件处理程序会解析类似<code>主题</code>或<code>body</code>的预置内容。</p>
<h3>13. Frames</h3>
<p>浏览器可以通过创建不同的<code>Frame</code>分割视图区域，并且每个<code>Frame</code>有单独的<code>HTML</code>结构，浏览器标签所有的<code>Frame</code>的集合被称为<code>frameset</code>。</p>
<blockquote>
<p><code>frameset</code>代替<code>body</code>标签，定义页面的行与列的布局。</p>
</blockquote>
<p>这种技术的缺点也很明显：</p>
<ul>
<li>小屏幕设备显示功能效果不好，难以让分割视图的特性发挥效果。</li>
<li>不同分辨率的终端显示效果可能不同</li>
<li>浏览器的返回按钮可能会出乎意料，甚至无法实现用户想要的效果</li>
<li>少数浏览器不支持此标签</li>
<li>跨<code>Frame</code>通信较为复杂</li>
</ul>
<p>示例:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;HTML Frames&lt;/title&gt;
  &lt;/head&gt;

  &lt;frameset cols=&quot;25%,50%,25%&quot;&gt;
    &lt;frame name=&quot;left&quot; src=&quot;https://www.baidu.com&quot; /&gt;
    &lt;frame name=&quot;center&quot; src=&quot;https://www.baidu.com&quot; /&gt;
    &lt;frame name=&quot;right&quot; src=&quot;https://www.baidu.com&quot; /&gt;

    &lt;noframes&gt;
      &lt;body&gt;
        Your browser does not support frames.
      &lt;/body&gt;
    &lt;/noframes&gt;
  &lt;/frameset&gt;
&lt;/html&gt;
</code></pre>
<p>还有一个标签：<code>iframe</code>也是提供一个内嵌的<code>frame</code>功能，不过它可以脱离<code>frameset</code>使用，更具有灵活性。</p>
<p>这种技术的应用较为少见，请谨慎使用（笔者在广告功能中常能见到其踪影）。</p>
<h3>14. 字体滚动</h3>
<p><code>marquee</code>的兼容性良好，但是此元素已经不再推荐使用，规范随时可能删除此标签，开发者们更应该通过<code>CSS</code>动画来实现文字滚动。</p>
<h3>15. HTML 通用属性</h3>
<blockquote>
<p>在 base、head、html、meta、param、script、style、title 元素上无效</p>
</blockquote>
<p>H5 之前有五个除了上述几个元素外其他所有元素共有的可用属性：</p>
<ul>
<li>class</li>
<li>id</li>
<li>style</li>
<li>title</li>
<li>tabindex</li>
<li>accesskey</li>
</ul>
<p>前四者较为常用，后两者却容易被忽视。<code>tabindex</code>属性用于获取或指定当前元素的<code>tab</code>键激活顺序，其值范围为：<code>0~32767</code>。</p>
<p>如果不设置，则默认值是 0，并且按出现顺序进行跳转，但是可以设置值为<code>-1</code>，使得元素不能被<code>TAB</code>键访问，如果某些元素是隐藏的，在其未显示之前不应该被<code>TAB</code>键访问到，因此可以设置<code>tabindex=-1</code>。</p>
<blockquote>
<p>如果为<code>div</code>设置了<code>tabindex</code>，则其内部嵌套的元素如果没有设置<code>tabindex</code>，则无法通过方向键跳转。</p>
</blockquote>
<p>如果针对某些元素设置了<code>tabindex</code>，则<code>tab</code>键将从大到小进行跳转，慎用此特性以免给惯用快捷键访问的用户造成困扰，但如果良好地设计<code>tabindex</code>，则可以提供良好的无障碍访问功能。</p>
<p>同样的，<code>accesskey</code>也容易被人忽略，即使其功能强大。</p>
<p>我们可以为元素设置<code>accesskey</code>的值，然后通过不同系统的浏览器快捷键+此属性的值进行快速访问。</p>
<p><img src="https://raw.githubusercontent.com/youyiqin/markdown_imgs/master/image-20210616060934949.png" alt="image-20210616060934949"></p>
<blockquote>
<p><a href="https://www.zhangxinxu.com/wordpress/2017/05/html-accesskey/comment-page-1/#comment-414139">HTML accesskey 属性与 web 自定义键盘快捷访问 « 张鑫旭-鑫空间-鑫生活</a></p>
</blockquote>
<p>如果你喜欢<code>vimium</code>的功能，我猜这个属性的功能能够让你很开心。但是很多应用都没有使用到这个属性，这是无障碍访问功能的一部分，常常被人忽视，使其看起来犹如屠龙之技。</p>
<p>并且不同浏览器之间快捷键不同，对于元素的交互行为也不一样，这两个缺陷使其应用性大打折扣。浏览器快捷键和操作系统之间的影响可以从上图中看出端倪，而行为上的不一致，最简明的例子就是<code>IE</code>浏览器和<code>chrome</code>浏览器之间对<code>&lt;a&gt;</code>元素的行为不一致。前者只是让其获得焦点，后者却可以触发点击行为。</p>
<p><a href="https://www.npmjs.com/package/accesskey">accesskey - npm</a>这里有一个支持增强<code>accesskey</code>功能和处理一致性行为的第三方库，或许以后用得上。</p>
<blockquote>
<p>不过话说回来，这个属性提醒了我可以在<code>electron</code>技术上使用这个功能，这样就可以减少使用系统快捷键绑定的逻辑代码。</p>
</blockquote>
<p><code>HTML5</code>版本出来之后，新增了部分全局属性：</p>
<ul>
<li>contenteditable：是否可编辑</li>
<li>Data-*：自定义的元素数据存储，可以配合<code>JavaScript</code>或者<code>CSS</code>属性选择器使用</li>
<li>draggable：实验中的属性，是否可以拖动</li>
<li>dropzone：较为少见的属性，不如使用此名第三方库（<a href="https://github.com/dropzone/dropzone">dropzone/dropzone: Dropzone is an easy to use drag'n'drop library. It supports image previews and shows nice progress bars.</a>）</li>
<li>hidden：常用</li>
<li>spellcheck：拼写检查，也是实验中的功能</li>
</ul>
<h3>16. 字体知识</h3>
<p>字体是相对于操作系统而言的，不同操作系统默认支持某些特定的字体，因此在样式表中我们可以指定多种字体以支持不同的操作系统，如果没有指定字体，则使用系统默认的字体。</p>
<p>CSS 定义了 5 个常用的字体名称: <code>serif, ``sans-serif, ``monospace</code>, <code>cursive,</code>和 <code>fantasy. </code>这些都是非常通用的，当使用这些通用名称时，使用的字体完全取决于每个浏览器，而且它们所运行的每个操作系统也会有所不同。</p>
<p>示例样式如下：</p>
<pre><code class="language-css">p {
  font-family: &quot;Trebuchet MS&quot;, Verdana, sans-serif;
}
</code></pre>
<p>提供了三种字体，前面的优先，如果系统不支持此字体则一次往后递增，最后使用默认字体。</p>
<p>我们可以使用<code>@font-face</code>自定义字体：</p>
<pre><code class="language-css">@font-face {
    font-family: &lt;fontFamily&gt;; /* 自定义的字体名称; */
    src: &lt;source&gt; [&lt;format&gt;][,&lt;source&gt; [&lt;format&gt;]]*;  /* 自定义的字体的存放路径、格式; */
    [font-weight: &lt;weight&gt;]; /*  是否为粗体 */
    [font-style: &lt;style&gt;]; /*  定义字体样式，如斜体 */
}
</code></pre>
<p>字体具有不同的格式，源文件格式可能会是：</p>
<ul>
<li>.tff</li>
<li>.otf</li>
</ul>
<p>前者字体格式值为<code>TrueType</code>，后者为<code>OpenType</code>，甚至还有：</p>
<ul>
<li>Embedded Open Type (.eot)</li>
<li>Web Open Font Format (.woff)</li>
</ul>
<p>为了保证兼容性，可以同时提供多种自定义字体：</p>
<pre><code class="language-css">@font-face {
  font-family: &quot;myFont&quot;;
  src: url(&quot;myFont.eot&quot;); /* IE9 Compat Modes */
  src: url(&quot;myFont.eot?#iefix&quot;) format(&quot;embedded-opentype&quot;), /* IE6-IE8 */
      url(&quot;myFont.woff&quot;) format(&quot;woff&quot;); /* Modern Browsers */
}
</code></pre>
<p>自定义字体的另一个广泛使用案例：<code>图标字体</code>。</p>
<p><img src="https://raw.githubusercontent.com/youyiqin/markdown_imgs/master/image-20210618003909597.png" alt="image-20210618003909597"></p>
<p>图标字体可以很方便的配合伪类使用，并且非常灵活，开发者可以轻松修改其颜色和大小等。另外，兼容性很好，在某些需要兼容<code>IE</code>的项目中可以放心使用。</p>
<p>使用图标字体还有以下几个优点：</p>
<ul>
<li>轻松替换</li>
<li>不会失真</li>
<li>便捷，可压缩</li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Sass 浅解]]></title>
            <link>https://i5lin.top/blog/post/1397546849</link>
            <guid isPermaLink="false">1397546849</guid>
            <pubDate>Wed, 05 Oct 2022 10:02:23 GMT</pubDate>
            <description><![CDATA[目前对 sass 编写样式表一知半解，在写手头的项目时十分不顺，效率低下，因此打算花两个小时重学 sass]]></description>
            <content:encoded><![CDATA[<h1>前言</h1>
<p>时至今日，<code>Sass</code>已经可以视为一门编程语言了。笔者目前对 sass 编写样式表一知半解，在写手头的项目时十分不顺，效率低下，因此打算花两个小时重学 sass。</p>
<p>某种意义上而言本文仅仅是笔者对于文档的学习和思索，更好的学习方法依然是阅读文档，但是于我于有兴趣阅读此文的朋友而言都一样，我都希望我们能够有所得。</p>
<h1>大纲</h1>
<ul>
<li>语法</li>
<li>样式规则</li>
<li>变量</li>
<li>操作</li>
<li>模块</li>
<li>断点和混入</li>
</ul>
<h2>语法</h2>
<h3>基础</h3>
<p>sass 支持两种语法，二者可以互相载入，分别是：</p>
<ul>
<li>scss</li>
<li>sass</li>
</ul>
<p>二者的区别从扩展名开始，<code>scss</code>对<code>css</code>的兼容性是最好的，几乎所有的<code>css</code>都是有效的<code>scss</code>写法（除了错误的语法被忽略的那部分），正因如此，此语法广受推崇。</p>
<blockquote>
<p>当你 copy 一段让人拍案叫绝的代码到自己的样式表中时，你什么都不需要做</p>
</blockquote>
<p>笔者喜欢<code>.sass</code>第二种语法，本文内容建议使用<code>.sass</code>来理解。笔者很懒，<code>.sass</code>可以少写些许花括号和分号，唯一需要注意的是<code>.sass</code>对缩进的写法非常严格，不过这不是什么大问题。</p>
<h3>解析样式表</h3>
<blockquote>
<p>A Sass stylesheet is parsed from a sequence of Unicode code points. It&#39;s parsed directly, without first being converted to a token stream.</p>
</blockquote>
<p><code>sass</code>需要通过编译器去编译成<code>css</code>，流行的编译器如下：</p>
<ul>
<li>Dart Sass</li>
<li>LibSass</li>
<li>Ruby Sass</li>
</ul>
<p>通常我们写样式都用不上除了英文和数字外的字符，上述编译器<code>Dart Sass</code>只支持<code>UTF-8</code>字符，只有当我们需要用超过<code>UTF-8</code>字符集的字符来写样式时，才需要考虑不使用<code>Dart Sass</code>。</p>
<p>当编译器遇到无效的语法时，解析会失败并且提供错误信息以便开发者修正错误语法。</p>
<h3>样式表结构</h3>
<p>除了和<code>css</code>类似的样式属性声明外，<code>sass</code>还支持许多增强型特性。</p>
<p><code>sass</code>由一系列的语句块组成，语句块之间可以嵌套其他语句块。</p>
<h4>语句</h4>
<p>我们可以将语句分为四种：</p>
<ul>
<li>css 语句<ul>
<li>样式规则</li>
<li>css @规则，例如 <code>@media</code></li>
<li>Mixin</li>
<li>@at-root</li>
</ul>
</li>
<li>普通语句<ul>
<li>变量声明 <code>$var: value</code></li>
<li>流程控制 <code>@if</code></li>
<li>规则控制 <code>@error</code>、<code>@debug</code>等</li>
</ul>
</li>
<li>顶层语句(样式表顶层或嵌套的 css 顶层)<ul>
<li>文件引入 <code>@import</code></li>
<li>混入 <code>@minxin</code></li>
<li>函数定义 <code>@function</code></li>
</ul>
</li>
<li>其他语句<ul>
<li><code>@extend</code>语句写在样式属性中</li>
</ul>
</li>
</ul>
<p>前者易于理解，跟<code>css</code>更为接近。后者形如则是<code>sass</code>的核心，有了这些才撑起了整体结构。</p>
<p>即使对上述词汇了解不多也不需要担心，接下来逐一拆解学习。</p>
<h4>表达式</h4>
<p>表达式语法即<code>SassScript</code>。</p>
<p>表达式位于属性或变量声明的右侧，表达式产生一个<code>值</code>。显然，任意有效的<code>css</code>值都可以理解为<code>sass</code>的表达式。</p>
<p>在<code>sass</code>中，表达式用于传给<code>mixin</code>或<code>function</code>,亦或配合<code>@if</code>控制流，甚至进行<code>算术运算</code>。</p>
<p>最简单的表达式只表示静态的值，例如：</p>
<ul>
<li>Numbers: 100px</li>
<li>String: &quot;bolder&quot;</li>
<li>Colors: #eee</li>
<li>Booleans: true or false</li>
<li>Lists: 1px 2px 0 1px</li>
<li>Maps: 键值对映射，例如<code>(&quot;color&quot;: red, &quot;font-size&quot;: 12px)</code></li>
</ul>
<p>其他表达式：</p>
<ul>
<li>变量<code>$var</code></li>
<li>函数调用<code>var(--color-bg-1)</code></li>
<li>特殊函数<code>calc(1px + 100%)</code>或<code>url(...)</code></li>
<li>父选择器 <code>&amp;</code></li>
<li><code>!important</code></li>
</ul>
<h3>操作符</h3>
<p><code>sass</code>支持许多操作符语法：</p>
<ul>
<li><code>== or !=</code>比较值</li>
<li><code>+ - * / % </code>数学计算</li>
<li><code>&lt; &lt;= &gt; &gt;=</code>逻辑判断</li>
<li><code>and or not</code>布尔值判断</li>
<li><code>+ - /</code>字符串拼接</li>
<li><code>()</code>优先级控制</li>
</ul>
<h3>注释</h3>
<p><code>sass</code>是编程语言，有注释很正常。</p>
<p>在 <code>scss</code> 中写注释，可以这样写：</p>
<pre><code class="language-scss">// 这个注释不会出现在CSS中

/* 这个将会出现在CSS中，压缩模式下不会有 */

/* CSS中显示插值的计算结果
 * 1 + 1 = #{1 + 1} */

/*! 在压缩模式下也会显示 */

p /* 多行注释可以写在任何允许 */
  /* 使用空格的地方。 */ .sans {
  font: Helvetica,
    // 单行注释也是
    sans-serif;
}
</code></pre>
<p>回到正题，在<code>sass</code>中更简洁：</p>
<pre><code class="language-scss">//  这个注释不会出现在CSS中
    这个也被注释掉了

/*  这个将会出现在CSS中，压缩模式下不会有

/*  CSS中显示插值的计算结果
 *  1 + 1 = #{1 + 1}

/*! 在压缩模式下也会显示

p .sans
  font: Helvetica, /* 内联注释必须是闭合的 */ sans-serif
</code></pre>
<blockquote>
<p>当你打算编写样式库时，可以在文档的最上方使用三斜杠<code>///</code>来开头编写文档，最终<code>SassDoc</code>等工具可以读取并且生成美观的文档。</p>
</blockquote>
<h3>特殊函数</h3>
<p>此前在表达式处提及特殊函数，在此重申之。</p>
<p><code>url()</code>是<code>css</code>中常用的函数，在<code>sass</code>中需要用特殊的逻辑来解析它。简单来看，可以按<code>css</code>规则来调用，也可以使用<code>sass</code>变量或函数调用最后的值来解析。举个例子：</p>
<pre><code class="language-scss">$roboto-font-path: &quot;../fonts/roboto&quot;

@font-face
    // This is parsed as a normal function call that takes a quoted string.
    src: url(&quot;#{$roboto-font-path}/Roboto-Thin.woff2&quot;) format(&quot;woff2&quot;)

    font-family: &quot;Roboto&quot;
    font-weight: 100


@font-face
    // This is parsed as a normal function call that takes an arithmetic
    // expression.
    src: url($roboto-font-path + &quot;/Roboto-Light.woff2&quot;) format(&quot;woff2&quot;)

    font-family: &quot;Roboto&quot;
    font-weight: 300
</code></pre>
<p>此外，还有其他的特殊函数：</p>
<p>css 中的**calc()**跟<code>sass</code>算法冲突，总之一句话，除了<code>sass</code>插值用于动态注入值之外，其他内容都作为原生<code>css</code>内容解析。</p>
<p>举个例子：</p>
<pre><code class="language-scss">.logo
  $width: 800px
  width: $width
  position: absolute
  left: calc(50% - #{$width / 2})
  top: 0
</code></pre>
<p>Sass 中的<code>min()和 max()</code>二者在<code>css</code>支持之前就实现了，当这类特殊函数中不包含除了插值之外的任何<code>SassScript</code>的特性时，解析为<code>css</code>原生函数。</p>
<p>举个例子：</p>
<pre><code class="language-scss">$padding: 12px

.post
  // Since these max() calls don&#39;t use any Sass features other than
  // interpolation, they&#39;re compiled to CSS max() calls.
  // 插值和原生的 env 函数
  padding-left: max(#{$padding}, env(safe-area-inset-left))
  padding-right: max(#{$padding}, env(safe-area-inset-right))


.sidebar
  // Since these refer to a Sass variable without interpolation, they call
  // Sass&#39;s built-in max() function.
  // 变量属于 SassScript
  padding-left: max($padding, 20px)
  padding-right: max($padding, 20px)
</code></pre>
<p>除此之外，还有众多内建函数，我们在配合相关知识点时候再总结。</p>
<h2>变量</h2>
<p><code>$&lt;variable&gt;:&lt;expression&gt; </code>即变量定义，使用<code>$</code>开头进行定义，表达式求值。</p>
<blockquote>
<p>Sass 标识符和变量名都将连字符<code>-</code>和下划线<code>_</code>视为相同的字符。</p>
</blockquote>
<p>任何地方都可以申明变量，<code>sass</code>的变量会被编译，因此一次只能有一个值，可以重新定义同名的变量，使用新值覆盖旧值，再使用新的变量和值，整个过程是命令式的。</p>
<p>在开发<code>sass</code>库时可以使用<code>!default</code>来编写变量默认值，普通开发者则无需关心。</p>
<p>举例：</p>
<pre><code class="language-scss">// _library.sass
$black: #000 !default
$border-radius: 0.25rem !default
$box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default

code
  border-radius: $border-radius
  box-shadow: $box-shadow
</code></pre>
<pre><code class="language-scss">// style.sass
$black: #222
$border-radius: 0.1rem

@import &#39;library&#39;
</code></pre>
<p>显然，我们在使用他人开发的库时，可以预先定义一些变量来个性化样式表。库的开发者也可以用默认样式兜底。</p>
<p>既然有变量，当然也有<code>变量作用域</code>。</p>
<p>一个<code>sass</code>文件顶层声明的变量是整个文件全局可访问的，并且当这个文件被其他文件引入时，顶层的变量将被共享。</p>
<p>在块中声明的变量却是本地可用的，在当前块内可用，当块内变量和全局变量同名时，在块内的优先级更高。</p>
<p>但是依然有办法灵活应用规则，举个例子：</p>
<pre><code class="language-scss">$variable: first global value;

.content {
  $variable: second global value !global;
  value: $variable;
}

.sidebar {
  value: $variable;
}
</code></pre>
<p>使用<code>!global</code>标志可以显式指定此变量为全局变量。</p>
<p>另外，在<code>流程控制</code>中声明的变量具有特殊的作用域，并且流程控制内只能<code>给外部存在的变量赋值</code>，而不能声明新的变量。</p>
<p>举个<code>scss</code>的例子：</p>
<pre><code class="language-scss">$dark-theme: true !default;
$primary-color: #f8bbd0 !default;
$accent-color: #6a1b9a !default;

@if $dark-theme {
  $primary-color: darken($primary-color, 60%);
  $accent-color: lighten($accent-color, 60%);
}

.button {
  background-color: $primary-color;
  border: 1px solid $accent-color;
  border-radius: 3px;
}
</code></pre>
<p>引入此库的其他文件可以自定义暗色主题来控制当前样式的结果。</p>
<p>还有一个问题，如何判断变量是否存在？<code>sass</code>提供了以下函数：</p>
<ul>
<li>Variable-exists()</li>
<li>Global-variable-exists()</li>
</ul>
<p>二者分别判断当前作用域内变量和全局变量。</p>
<h2>插值</h2>
<p>只要我们需要将表达式的结果嵌入到<code>css</code>块中去，就可以使用<code>#{}</code>包装的表达式，这种语法被称为插值。</p>
<p>我们可以在任何地方使用插值，插值就像表达式替换。<code>sass</code>以一种直白的方式来让插值生效，举个例子：</p>
<pre><code class="language-scss">@mixin corner-icon($name, $top-or-bottom, $left-or-right) .icon-#{$name} background-image:
  url(&quot;/icons/#{$name}.svg&quot;) position: absolute #{$top-or-bottom}: 0 #{$left-or-right}:
  0 @include corner-icon(&quot;mail&quot;, top, right);
</code></pre>
<p>先不论<code>mixin</code>和<code>include</code>是什么，无论你传入什么值过去，编译后将直接在原位置进行替换：</p>
<pre><code class="language-css">.icon-mail {
  background-image: url(&quot;/icons/mail.svg&quot;);
  position: absolute;
  top: 0;
  left: 0;
}
</code></pre>
<blockquote>
<p>需要注意的是，当插值花括号中包含引号时，带引号的字符串周围的引号会被删除。</p>
</blockquote>
<h2>样式规则</h2>
<p>进入正题。</p>
<blockquote>
<p><code>css</code>也已经存在嵌套的草案，很棒。</p>
</blockquote>
<p><code>sass</code>支持嵌套，这是<code>sass</code>随处可见的知识点，但凡看过 sass 的开发者都知道，无需赘述。关键在于：<code>控制嵌套深度 </code>，嵌套很棒但过深的嵌套生成的<code>css</code>代码就越多，三层足以避免过犹不及。</p>
<p>选择器列表支持和组合选择器可以直接使用<code>css</code>语法。</p>
<p>举个例子 🌰：</p>
<pre><code class="language-scss">.enlarge
  font-size: 14px
	margin: 1rem
		top: 2rem
  transition:
    property: font-size
    duration: 4s
    delay: 2s

  &amp;:hover
    font-size: 36px
	&amp; &gt; h1
		color: #333
	&amp;r
		color: purple

	.other
		color: red
</code></pre>
<p>上述<code>enlarge</code>类内嵌套了<code>other</code>类，并且使用了<code>&amp;</code>父节点选择器实现了<code>.enlarge:hover</code>、<code>.enlarge &gt; h1</code>甚至是<code>.enlarger</code>这样的选择器效果。</p>
<p>其中<code>margin</code>属性名还可以视为命名空间嵌套着使用，编译出<code>margin-top: 2rem</code>的样式。</p>
<p>此外，编译器都能对多个父选择器做出判断：</p>
<pre><code class="language-scss">ul, ol
  text-align: left

  &amp; &amp;
    padding:
      bottom: 0
      left: 0
</code></pre>
<p>上述父选择器按顺序进行编译，不必担心<code>ul</code>和<code>ol</code>在使用父选择器时出现顺序异常。</p>
<p><code>&amp;</code>父选择器可以作为判断条件，例如：</p>
<p><code>if(&amp;, &#39;&amp;.app-background&#39;, &#39;.app-background&#39;)</code></p>
<p>当前处于子级范围时，采用前者，否则采用后者。</p>
<blockquote>
<p>即使是 css，选择器就已经非常强大了。</p>
</blockquote>
<p>此外，还可以根据特定的条件去设置属性和值，此时声明一个<code>null</code>作为表达式，最终将不会编译此属性。</p>
<p>举个例子：</p>
<pre><code class="language-scss">$rounded-corners: false

.button
  border: 1px solid black
  border-radius: if($rounded-corners, 5px, null)
</code></pre>
<p>最终的<code>css</code>文件将不包含<code>border-radius</code>属性。</p>
<p><code>css</code>支持<code>自定义属性</code>，也称为<code>css</code>变量。这个机制允许开发者灵活的设置属性，同时<code>JavaScript</code>能够访问这些值。</p>
<p>在<code>sass</code>中对<code>css</code>自定义属性的处理需要关注的核心只有一个：仅有插值是动态的，其他标记都将原样编译为<code>css</code>，举个例子：</p>
<pre><code class="language-scss">$primary: #81899b
$accent: #302e24
$warn: #dfa612
// 插值将顺利转换
:root
  --primary: #{$primary}
  --accent: #{$accent}
  --warn: #{$warn}

  // Even though this looks like a Sass variable, it&#39;s valid CSS so it&#39;s not
  // evaluated.
  // 下一行将原样编译为 css
  --consumed-by-js: $primary
</code></pre>
<p>此外，还需要了解一个非常强大的<code>sass</code>选择器：<code>占位符</code>选择器。</p>
<p>占位符选择器和其他<code>css</code>选择器类似，它以<code>%</code>开头，并且不会包含在<code>css</code>输出中，举个例子：</p>
<pre><code class="language-scss">.alert:hover, %strong-alert
  font-weight: bold


%strong-alert:hover
  color: red
</code></pre>
<p>最终编译的结果如下：</p>
<pre><code class="language-css">.alert:hover {
  font-weight: bold;
}
</code></pre>
<p>但是，我们却可以在<code>sass</code>中引用其内容对其他选择器进行扩展，减少代码量，举个例子：</p>
<pre><code class="language-scss">%toolbelt
  box-sizing: border-box
  border-top: 1px rgba(#000, .12) solid
  padding: 16px 0
  width: 100%

  &amp;:hover
    border: 2px rgba(#000, .5) solid

.action-buttons
  @extend %toolbelt
  color: #4285f4

.reset-buttons
  @extend %toolbelt
  color: #cddc39
</code></pre>
<p>如上所示，<code>.action-buttons</code>和<code>.reset-button</code>都被扩展了，最后的编译结果为：</p>
<pre><code class="language-css">.action-buttons,
.reset-buttons {
  box-sizing: border-box;
  border-top: 1px rgba(0, 0, 0, 0.12) solid;
  padding: 16px 0;
  width: 100%;
}
.action-buttons:hover,
.reset-buttons:hover {
  border: 2px rgba(0, 0, 0, 0.5) solid;
}

.action-buttons {
  color: #4285f4;
}

.reset-buttons {
  color: #cddc39;
}
</code></pre>
<p>显然我们可以灵活编写占位符选择器，并且选择适当的情况下<code>扩展</code>其他选择器，还不用关心占位符选择器这部分代码，因为最终在不引用的情况下会忽略这部分而编译整体。</p>
<h2>模块化</h2>
<p>编程语言模块化可以将复杂的单文件拆分成多个小文件，将复杂环境拆分梳理为若干小环境，降低开发者的心智压力。</p>
<p>按照惯例，开发者将以<code>下划线</code>开头的<code>sass</code>文件视为<code>片段文件</code>，这些片段文件将被<code>@import</code>指令使用，如果不然<code>sass</code>编译器将忽略编译这些片段文件。</p>
<p>一句话，使用下划线开头的文件作为片段文件，在需要用到片段文件的<code>sass</code>文件使用<code>@import</code>指令将之引入，引入时需要<code>忽略下划线</code>。</p>
<blockquote>
<p>css 具有自己的 @import 规则，浏览器在解析到 css 的导入行为时将会发起一个 http 请求获取此目标文件。</p>
</blockquote>
<p><code>sass</code>将会获取目标导入文件，并且将此文件和被导入的文件结合，编译出最终样式表。</p>
<p>当将片段文件导入到目标文件后，目标文件可以使用片段文件中的内容。</p>
<h2>规则</h2>
<p>Sass 的大部分额外功能都是在 CSS 之上添加新<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/At-rule">@规则</a></p>
<p>以下是我们可以在日常开发中使用起来的规则：</p>
<ul>
<li>@import 引入样式、mixin、函数、变量</li>
<li>@mixin、@include 增强复用样式块</li>
<li>@function 增强内置函数外的函数功能</li>
<li>@at-root 将样式放入 CSS 文档的根目录中</li>
<li>@error、@warn、@debug 用于调试</li>
<li>@if、@each、@for、@while 控制函数逻辑</li>
</ul>
<p>接着，我们逐一加深这些知识点的印象。</p>
<h3>@import</h3>
<p>原生的<code>css</code>让浏览器呈现时增加<code>http</code>请求，而<code>sass</code>则在编译期间解决问题。</p>
<p>导入多个文件时可以添加逗号作为分隔，使用<code>.sass</code>语法时不需要为<code>URL</code>添加括号。并且导入的文件内容将会放在<code>@import</code>指令出现的位置，但嵌套导入的 mixin、函数、变量依然具有原作用域。</p>
<p>另外，对于导入的文件<code>URL</code>，编译器都能很好的自动进行忽略扩展名的文件查找，并且在路径上统一使用<code>/</code>作为分隔符。开发者不需要为不同平台的地址问题费心思了。</p>
<p>对于加载路径的优先级而言，本地文件的优先级高于<code>node_modules</code>下的库文件。</p>
<p>如果导入的是一个<code>css</code>文件（本地或远程）亦或使用了<code>url()</code>或媒体查询时，最终将会直接作为原生的<code>css</code>导入使用。</p>
<h3>@mixin</h3>
<p>通过<code>@mixin</code>定义一个具名的<code>mixin</code>，通过<code>@include mixinName</code> 使用<code>mixin</code>。</p>
<blockquote>
<p>从笔者个人层面来看，<code>mixin</code>就是<code>sass</code>这门语言的&quot;函数&quot;</p>
</blockquote>
<p><code>mixin</code>能够让我们轻松通过<code>复用</code>机制减少非语义类的使用，并且<code>mixin</code>非常灵活。</p>
<p>接下来我直接看几个<code>mixin</code>及其用法：</p>
<pre><code class="language-scss">@mixin square($size, $radius: 0) width: $size height: $size @if $radius != 0 border-radius:
  $radius .avatar @include square(100px, $radius: 4px);
</code></pre>
<p>如上所示，可以预定义一个<code>mixin</code>，参数支持具有默认值的可选参数，这一点跟<code>JS</code>很像。</p>
<p>再看一个示例：</p>
<pre><code class="language-scss">@mixin square($size, $radius: 0, $n: 1) width: $size height: $size @if $n != 1 border:
  #{$n}px .avatar @include square(100px, $n: 4);
</code></pre>
<p>可以传递具名的参数，这样可以忽略传参的顺序。</p>
<p>再看：</p>
<pre><code class="language-scss">@mixin order($height, $selectors...) @for $i from 0 to length($selectors) #{nth(
    $selectors,
    $i + 1
  )} position: absolute height: $height margin-top: $i * $height @include order(150px, &quot;input.name&quot;, &quot;input.address&quot;, &quot;input.zip&quot;);
</code></pre>
<p>如果最后一个参数以<code>...</code>结尾，即可将传入的额外参数视为一个可以迭代的列表。</p>
<blockquote>
<p>nth 是内建函数</p>
</blockquote>
<p>最终的编译结果为：</p>
<pre><code class="language-css">input.name {
  position: absolute;
  height: 150px;
  margin-top: 0px;
}

input.address {
  position: absolute;
  height: 150px;
  margin-top: 150px;
}

input.zip {
  position: absolute;
  height: 150px;
  margin-top: 300px;
}
</code></pre>
<p>再看一个<code>.scss</code>示例：</p>
<pre><code class="language-scss">@mixin syntax-colors($args...) {
  @debug keywords($args); // (string: #080, comment: #800, $variable: $60b)

  @each $name, $color in keywords($args) {
    pre span.stx-#{$name} {
      color: $color;
    }
  }
}

@include syntax-colors($string: #080, $comment: #800, $variable: #60b);
</code></pre>
<p>传递多个参数，<code>mixin</code>的定义参数位不定键名参数，最终可以使用内建的函数获取到键值对。</p>
<p>再看：</p>
<pre><code class="language-scss">@mixin btn($args...) {
  @warn &quot;The btn() mixin is deprecated. Include button() instead.&quot;;
  @include button($args...);
}
</code></pre>
<p>看到这里，是否能感受到我们可以使用<code>sass</code>很方便的创建某个<code>UI</code>库的样式表，可以为团队创建基础的<code>sass</code>片段。</p>
<p>另外，<code>mixin</code>还支持内容块，只需要在<code>mixin</code>中添加一行<code>@content</code>即可，举个例子：</p>
<pre><code class="language-scss">@mixin hover &amp;: not([disabled]): hover @content .button border: 1px solid black
  @include hover border-width: 2px;
</code></pre>
<p>到这里我已经觉得复杂了，还可以支持多个<code>@content</code>和给内容块传参，善用<code>sass</code>真不容易，需要大量的使用场景和解决问题的经验。</p>
<h3>@function</h3>
<p>通过<code>@function</code>来定义函数，并且可以在任何地方直接调动函数，就像我们使用<code>css</code>原生的函数一样。</p>
<p>此前笔者提及<code>mixin</code>如<code>sass</code>的函数，然而实际上<code>sass</code>的函数由<code>@function</code>定义，二者是单独的概念。</p>
<p>来看一个基础函数<code>pow</code>：</p>
<pre><code class="language-scss">@function pow($base, $exponent) $result: 1 @for $_ from 1 through $exponent $result:
  $result * $base @return $result .sidebar float: left margin-left: pow(4, 3) * 1px;
</code></pre>
<p>如其名，计算乘方。关注函数的标识、参数、结构控制、返回值，这部分跟<code>mixin</code>是一样的。</p>
<p>此外，<code>sass</code>内建了诸多强大的函数方便我们开发使用，针对不同类型有：</p>
<ul>
<li>颜色</li>
<li>列表</li>
<li>映射表</li>
<li>数学模块</li>
<li>meta</li>
<li>选择器</li>
<li>字符串</li>
</ul>
<p><code>sass</code>有诸多语法和数据类型，根据这些数据可以配合这部分内建函数处理问题，例如我们需要调整一个颜色值：</p>
<pre><code class="language-scss">color.adjust($color,
  $red: null, $green: null, $blue: null,
  $hue: null, $saturation: null, $lightness: null,
  $whiteness: null, $blackness: null,
  $alpha: null)
adjust-color(...) //=&gt; color

@debug color.adjust(#6b717f, $red: 15); // #7a717f
@debug color.adjust(#d2e1dd, $red: -10, $blue: 10); // #c8e1e7
@debug color.adjust(#998099, $lightness: -30%, $alpha: -0.4); // rgba(71, 57, 71, 0.6)
</code></pre>
<p>这个内建函数实在是强大而方便，此刻笔者不必赘述，我也非常欠缺使用经验。</p>
<h3>@extend</h3>
<p><code>sass</code>继承指的是让某个选择器继承另一个选择器的样式。关键在于扩展的目标类是否有效，其中的扩展机制能够保证以下几点：</p>
<ul>
<li>不会生成匹配不到任何元素的选择器</li>
<li>它确保复杂的选择器是交错的，这样无论嵌套 HTML 元素的顺序如何，它们都可以工作。</li>
<li>它尽可能地减少冗余选择器，同时仍然确保选择器的特性大于或等于被扩展的选择器的特性。</li>
<li>它知道一个选择器何时匹配另一个选择器所做的一切，并可以将它们组合在一起。</li>
<li>它智能地处理<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Selectors#Combinators">组合选择器</a>、<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/Universal_selectors">通用选择器</a>和<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/:not">:not 选择器</a>。</li>
</ul>
<p>举个例子：</p>
<pre><code class="language-scss">.content nav.sidebar
  @extend .info

// 不会扩展，因为“p”与“nav”不兼容。
p.info
  background-color: #dee9fc

// 没有办法知道.guide和.content的层级关系，因此Sass安全起见会生成两种选择器。
.guide .info
  border: 1px solid rgba(#000, 0.8)
  border-radius: 2px

// Sass知道每个匹配“main”的元素。也匹配“.content”。并避免生成不必要的交错选择器。
main.content .info
  font-size: 0.8em
</code></pre>
<p>最后的结果为：</p>
<pre><code class="language-css">p.info {
  background-color: #dee9fc;
}

.guide .info,
.guide .content nav.sidebar,
.content .guide nav.sidebar {
  border: 1px solid rgba(0, 0, 0, 0.8);
  border-radius: 2px;
}

main.content .info,
main.content nav.sidebar {
  font-size: 0.8em;
}
</code></pre>
<p>最后扩展的结果非常难以理解（对于我来说），在有必要的时候尽可能写让大多数人易于理解的扩展吧。</p>
<h3>调试和信息</h3>
<ul>
<li>@error</li>
<li>@warn</li>
<li>@debug</li>
</ul>
<p>如上所示，都用于在编译时输出信息或者在调试时检查数据。</p>
<h1>参考</h1>
<ul>
<li><a href="http://www.sass-china.com/">Sass 中文文档</a></li>
<li><a href="https://www.sassmeister.com/">SassMeister | The Sass Playground!</a></li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[坚持做事也需要补充能量]]></title>
            <link>https://i5lin.top/blog/post/1516393502</link>
            <guid isPermaLink="false">1516393502</guid>
            <pubDate>Mon, 02 Jan 2023 13:22:08 GMT</pubDate>
            <description><![CDATA[负重前行也需要回到港湾补充能量，珍惜自己的港湾。]]></description>
            <content:encoded><![CDATA[<p><img src="https://images.unsplash.com/photo-1515634928627-2a4e0dae3ddf?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h2>windows 系统资源</h2>
<ul>
<li><a href="https://msdn.itellyou.cn/">MSDN, 我告诉你 - 做一个安静的工具站</a></li>
<li><a href="https://tb.rg-adguard.net/public.php">TechBench by WZT (v4.1.1)</a></li>
</ul>
<p>原版 Windows 系统和软件下载，需要纯净版资源的可以在这里下载。</p>
<h2>临时云盘</h2>
<blockquote>
<p><a href="https://tmp.link/">钛盘 - 超好用的文件中转站</a></p>
</blockquote>
<p>不限速不限空间的临时限时云盘</p>
<h2>EPUB 阅读器</h2>
<blockquote>
<p><a href="https://www.neat-reader.cn/">EPUB 阅读器 | TXT 阅读器 | Neat Reader | 超赞的全平台电子书阅读器，全球百万用户共同的选择</a></p>
</blockquote>
<p>免费版本的阅读器就够用了，经常看电子书的可以看看。</p>
<h2>RSS 阅读器</h2>
<blockquote>
<p><a href="https://hyliu.me/fluent-reader/">Fluent Reader</a></p>
</blockquote>
<p>作者还是伯克利的学生</p>
<h2>WeekTodo</h2>
<blockquote>
<p><a href="https://weektodo.me/">WeekToDo | Free Minimalist Weekly Planner App</a></p>
</blockquote>
<p>待办事项工具，简单有效。</p>
<h2>React Vditor 编辑器</h2>
<blockquote>
<p><a href="https://goer.icu/react-vditor/">react-vditor demo</a></p>
</blockquote>
<p>在开发者群体中，<code>markdown</code>语法非常流行，同时<code>typora</code>编辑器也很有知名度，其比大多数编辑器更吸引人的关键，或许是实时渲染、所见即所得的功能。</p>
<p><code>React-vditor</code> 是一个支持即时渲染的<code>react</code>编辑器组件，如果有使用<code>react</code>开发支持<code>markdown</code>编辑功能的产品，<code>React-vditor</code>不会让你失望。</p>
<h2>React-icons</h2>
<blockquote>
<p><a href="https://react-icons.github.io/react-icons">react-icons</a> 项目地址</p>
</blockquote>
<p>在前端开发中，<code>icon</code>几乎是绕不过的点，UI 设计中有许多地方用得上。<code>react-icons</code>项目有一系列的免费<code>icon</code>提供给开发者使用。</p>
<h2>react 滚动条</h2>
<blockquote>
<p><a href="https://minjieliu.github.io/mac-scrollbar/">Mac-like scrollbar demo</a></p>
</blockquote>
<p>国人开发的一款类 Mac 滚动条的 React 组件，非常适合美化和统一浏览器滚动条样式的需求。</p>
<h2>Windows 终端体验优化</h2>
<p><a href="https://juejin.cn/post/7108282550405038111">Windows 下终端体验优化浅解 - 掘金</a></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[极度自律的人至少分为三种：假装的、自闭的、精神病]]></title>
            <link>https://i5lin.top/blog/post/1517496373</link>
            <guid isPermaLink="false">1517496373</guid>
            <pubDate>Tue, 03 Jan 2023 14:50:22 GMT</pubDate>
            <description><![CDATA[极度自律的人至少分为三种：假装的、自闭的、精神病（:

![](https://images.unsplash.com/photo-1613475104618-94f6321a19c7?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80)

###]]></description>
            <content:encoded><![CDATA[<p>极度自律的人至少分为三种：假装的、自闭的、精神病（:</p>
<p><img src="https://images.unsplash.com/photo-1613475104618-94f6321a19c7?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h3>极简风博客</h3>
<p><a href="https://github.com/marcantondahmen/automad-terminal">marcantondahmen/automad-terminal: A minimal Automad blog theme for terminal lovers</a>：一个基于<code>PHP</code>的极简风格的博客系统，简单明了的色系搭配让我眼前一亮，给我一种技术的简单和纯粹的感觉（因此周日花了六小时用 React + TS 实现了一个版本，好累歇会）</p>
<h3>JavaScript 函数式编程</h3>
<p><a href="https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/">Introduction · 函数式编程指北</a>，学习 JavaScript 函数式编程可以让我们的代码在某些场景下更简洁有效，稳定且易于测试。</p>
<h3>React 状态管理库</h3>
<p><a href="https://jotai.org/">Jotai, primitive and flexible state management for React</a>：基于 React 元数据的状态管理库，非常灵活和简洁，相比其他库来说可以少写很多格式化代码。</p>
<p><a href="https://github.com/nanxiaobei/resso/blob/main/README.zh-CN.md">resso/README.zh-CN.md at main · nanxiaobei/resso</a>：号称世界上最简单的 React 状态管理库，支持 React 18、React Native、SSR、小程序。</p>
<h3>汉语拼音库</h3>
<p><a href="https://theajack.github.io/cnchar/">cnchar (v3.2.2)</a>：功能全面，多端支持的汉字拼音笔画 JS 库，如果有相关方面的需求可以看看这个库。</p>
<h3>3D 悬停效果</h3>
<p><a href="https://atroposjs.com/">Atropos - Stunning touch-friendly 3D parallax hover effects</a>：web 3D 悬停特效动画库，多框架支持。</p>
<h3>渐变动画示例</h3>
<p><a href="https://hypercolor.dev/">Gradients for Tailwind CSS | Hypercolor</a>：思路枯竭了吗？这里有一些渐变背景色挺不错的。</p>
<h3>Flutter 资源</h3>
<p><a href="https://zhuanlan.zhihu.com/p/545521148">Flutter 资源大全 - 组件、导航、模板、插件、框架和引擎，应有尽有 - 知乎</a></p>
<h3>404 页面示例</h3>
<p><a href="https://blog.stackfindover.com/html-css-404-page-templates/">35+ HTML 404 Page Templates - Stackfindover</a>：学了这几十个错误页面示例，以后再也不用害怕没思路。</p>
<h3>Until</h3>
<p><a href="https://getuntil.app/">Until App</a>：倒数日 APP，记录某个事件倒数计划，可以为苹果设备添加挂件。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[我们的“互联网”犹如一个巨大的孤岛，巨大到里面的大多数人都以为互联网上只有本国人，坐着井底看着天空。]]></title>
            <link>https://i5lin.top/blog/post/1517489827</link>
            <guid isPermaLink="false">1517489827</guid>
            <pubDate>Tue, 03 Jan 2023 14:45:25 GMT</pubDate>
            <description><![CDATA[我们的“互联网”犹如一个巨大的孤岛，巨大到里面的大多数人都以为互联网上只有本国人，坐着井底看着天空。

![](https://images.unsplash.com/photo-1485965373059-f07657e9f841?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=]]></description>
            <content:encoded><![CDATA[<p>我们的“互联网”犹如一个巨大的孤岛，巨大到里面的大多数人都以为互联网上只有本国人，坐着井底看着天空。</p>
<p><img src="https://images.unsplash.com/photo-1485965373059-f07657e9f841?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h3>React + TypeScript 博客</h3>
<p><a href="https://react-typescript-cheatsheet.netlify.app/">React TypeScript Cheatsheets | React TypeScript Cheatsheets</a>：React + TypeScript 开发者备忘录，你需要知道的<code>React + TypeScript </code>知识几乎都在这了。</p>
<h3>React项目学习资源</h3>
<p><a href="https://github.com/alan2207/bulletproof-react">alan2207/bulletproof-react: 🛡️ ⚛️ A simple, scalable, and powerful architecture for building production ready React applications.</a>：一个简单、灵活度高、结构良好，可以作为学习 TypeScript + React 开发的必备入门项目，我从中学到了很多。</p>
<h3>Vue 学习指南</h3>
<p><a href="https://learnvue.co/tutorials">Tutorials - LearnVue</a>：囊括了大多数 Vue 开发所需要的知识体系，阅读这些英文文档很有好处。</p>
<h3>原子化CSS引擎</h3>
<p><a href="https://github.com/unocss/unocss">unocss/unocss: The instant on-demand atomic CSS engine.</a>：知名开发者<code>antfu</code>力作，让我们更好地写<code>CSS</code>。</p>
<h3>Cerebro</h3>
<p><a href="https://cerebroapp.com/">Cerebro App – open-source productivity booster with a brain</a>：开源的资源引导工具，可以用以代替<code>Alfred</code>。</p>
<h3>Xnip</h3>
<p><a href="https://www.xnipapp.com/">Xnip | Handy Screenshot App for Mac</a>: 别出心裁的Mac截图工具，支持在桌面最顶层钉住最近的截图，便于查看。</p>
<h3>些许资源</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=UmVec9VHtpE&list=PLnTPdMjBRmAYehJkVbAXqxO-0cc9ALC6V">区块链课程 - YouTube</a>：北大陈老师的区块链视频课程，录制于老师上课现场</li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Vue的编程挑战]]></title>
            <link>https://i5lin.top/blog/post/1517485228</link>
            <guid isPermaLink="false">1517485228</guid>
            <pubDate>Tue, 03 Jan 2023 14:41:55 GMT</pubDate>
            <description><![CDATA[入职了字符跳跃后，我的技术栈从`React`切换到了`Vue`，也因此让我对`Vue`有了更多接触和认识。OK，`React`最棒，但`Vue`也不差！

![](https://images.unsplash.com/photo-1653387141060-9a9834f47777?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGV]]></description>
            <content:encoded><![CDATA[<p>入职了字符跳跃后，我的技术栈从<code>React</code>切换到了<code>Vue</code>，也因此让我对<code>Vue</code>有了更多接触和认识。OK，<code>React</code>最棒，但<code>Vue</code>也不差！</p>
<p><img src="https://images.unsplash.com/photo-1653387141060-9a9834f47777?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h3>Vue挑战</h3>
<p><a href="https://cn-vuejs-challenges.netlify.app/">Vue.js 挑战 | 一个 Vue.js 在线挑战平台</a>：对于vue开发者来说还挺有趣的，并且能够提升自己的经验。</p>
<h3>Vue 脚手架</h3>
<p><a href="https://vitecamp.netlify.app/">Vitecamp</a>：一个国人创建的 <code>Vue</code> 脚手架，支持：</p>
<ul>
<li>Vue3</li>
<li>TS</li>
<li>Element-Plus</li>
<li>Vue Router 4</li>
<li>Pinia</li>
<li>Auto Icon</li>
<li>Windi CSS</li>
<li>Axios</li>
<li>I18n</li>
<li>ESLint + Prettier</li>
<li>Husky</li>
<li>NProgress</li>
<li>VueUse</li>
</ul>
<p>功能齐全，非常方便。</p>
<h3>React Toast</h3>
<p><a href="https://react-local-toast.netlify.app/">Intro | React Local Toast</a>：不同于其他顶层信息提示，这是个基于包装的标签位置的本地提示弹出组件。</p>
<h3>网站动画背景</h3>
<p><a href="https://www.vantajs.com/?effect=birds">Vanta.js - Animated 3D Backgrounds For Your Website</a>：短短几行代码即可应用令人炫目的动画背景特效</p>
<h3>web 编辑器</h3>
<p><a href="https://github.com/xjh22222228/awesome-web-editor">xjh22222228/awesome-web-editor: 🔨 Open source WEB editor summary</a>：一系列多框架多风格多功能的web编辑器列表，可以选用作为自己产品的编辑器。</p>
<h3>Github Commit Emoji</h3>
<p><a href="https://gitmoji.dev/">gitmoji | An emoji guide for your commit messages</a>: 为你的提交添加一个适宜的emoji吧~</p>
<h3>IconJar</h3>
<p><a href="https://geticonjar.com/">IconJar • Your icon organizer for macOS</a>：web 开发者的 Icon 管理工具，但是（年费 170RMB）</p>
<h4>MacOS 应用清理工具</h4>
<p><a href="https://freemacsoft.net/appcleaner/">AppCleaner</a>：免费强大的APP清理工具</p>
<h3>MacOS 归档工具</h3>
<p><a href="https://aptonic.com/">Aptonic - Dropzone 4</a>：方便地移动和复制文件，归档任务，启动程序、上传多种服务等</p>
<h3>MacOS 应用切换</h3>
<p><a href="https://bahoom.com/hyperswitch">HyperSwitch</a>：像 <code>Windows</code>一样切换应用</p>
<h4>MacOS 应用缩放尺寸</h4>
<p><a href="https://manytricks.com/moom/">Moom · Many Tricks</a>：缩放和移动应用窗口，便于操作</p>
<h3>些许博客</h3>
<ul>
<li><p><a href="https://linmi.cc/42154.html">实体图书扫描导入 Notion 实践 · 豆瓣评分版 - Linmi</a>：notion 用户望周知，可以方便地通过iPhone的快捷操作扫书籍的二维码，将书籍导入到notion，自动建立自己的书库记录。</p>
</li>
<li><p><a href="https://github.com/fang0jun/Blog/issues/27">浮点数详解(IEEE 754标准） · Issue #27 · fang0jun/Blog</a></p>
</li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[工作渐忙]]></title>
            <link>https://i5lin.top/blog/post/1516414381</link>
            <guid isPermaLink="false">1516414381</guid>
            <pubDate>Mon, 02 Jan 2023 13:45:39 GMT</pubDate>
            <description><![CDATA[内容不多，本周没学到太多工作之外的东西（人变得有点懒惰，但是还是明白有很多事要做，一步步）

![](https://images.unsplash.com/photo-1519861531473-9200262188bf?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&]]></description>
            <content:encoded><![CDATA[<p>内容不多，本周没学到太多工作之外的东西（人变得有点懒惰，但是还是明白有很多事要做，一步步）</p>
<p><img src="https://images.unsplash.com/photo-1519861531473-9200262188bf?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2500&q=80" alt=""></p>
<h3>React DnD 库</h3>
<p><a href="https://react-beautiful-dnd.netlify.app/?path=/story/single-vertical-list--basic">single vertical list - basic ⋅ Storybook</a>：React 下的拖动库，简洁灵活，值得学习和使用。</p>
<h3>Web 动画技术</h3>
<p><a href="https://lottiefiles.com/">Free Lottie Animation Files, Tools &amp; Plugins - LottieFiles</a>：轻量、灵活的动画技术，完美替代<code>GIF</code></p>
<h3>分轨</h3>
<p><a href="https://ifengui.com/">分轨 | 上传文件，即刻分离人声和伴奏</a>：如果你想要一首歌的伴奏</p>
<h3>像素风格壁纸站</h3>
<p><a href="https://novocom.club/view/7626db-pixel-gif-wallpaper-for-pc/">Pixel Gif Wallpaper For Pc - Novocom.top</a>：如果你想要一张像素风的壁纸</p>
<h3>Git 仓库徽章</h3>
<p><a href="https://shields.io/">Shields.io: Quality metadata badges for open source projects</a>：为你的<code>Git</code>仓库添加喜欢的徽章吧。</p>
<h3>开源免费的 SVG 图片</h3>
<p><a href="https://undraw.co/illustrations">Illustrations | unDraw</a>：为你的项目添加一些有趣精美的免费<code>SVG</code>图片</p>
<h3>RSS 阅读器</h3>
<p><a href="https://reederapp.com/">Reeder 5</a>：Mac 平台下很好用的<code>RSS</code>阅读器</p>
<h3>些许博客</h3>
<ul>
<li><a href="http://fisherlei.blogspot.com/">水中的鱼</a>：算法、编程、博客分享</li>
<li><a href="https://www.jstips.co/zh_CN/">Js Tips - A JavaScript tip per day!</a>：日常分享，每天都能学到 JavaScript 的知识</li>
</ul>
<h5>最后</h5>
<p>😰 昨天忘了发！！！</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[撤退的发际线]]></title>
            <link>https://i5lin.top/blog/post/1516409042</link>
            <guid isPermaLink="false">1516409042</guid>
            <pubDate>Mon, 02 Jan 2023 13:39:06 GMT</pubDate>
            <description><![CDATA[我发现我发际线快撑不住了，这克制住了我熬夜的欲望。（实际上只坚持了半个月，后来熬夜依旧，怎么办

![](https://images.unsplash.com/photo-1615232784987-5de0d5f33dd6?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=cro]]></description>
            <content:encoded><![CDATA[<p>我发现我发际线快撑不住了，这克制住了我熬夜的欲望。（实际上只坚持了半个月，后来熬夜依旧，怎么办</p>
<p><img src="https://images.unsplash.com/photo-1615232784987-5de0d5f33dd6?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h3>令人惊叹的开源<code>web editor</code>汇总</h3>
<p><a href="https://github.com/xjh22222228/awesome-web-editor#Markdown-editor">xjh22222228/awesome-web-editor: 🔨 Open source WEB editor summary</a></p>
<p>在这里或许可以找到你的项目中可用的<code>markdown</code>编辑器、或是其他富文本编辑器开源作品。</p>
<h3>免费中文编程书单</h3>
<p><a href="https://github.com/justjavac/free-programming-books-zh_CN">justjavac/free-programming-books-zh_CN: 免费的计算机编程类中文书籍，欢迎投稿</a></p>
<h3>周报</h3>
<p><a href="about:blank#blocked">可乐周报 | Revue</a>：获取新知识，专注分享作者的视野。</p>
<h3>开源的跨平台多数据库 GUI 软件</h3>
<p><a href="https://www.beekeeperstudio.io/">Open Source SQL Editor and Database Manager | Beekeeper Studio</a>，支持以下一系列数据库</p>
<ul>
<li>PostgreSQL</li>
<li>MySQL</li>
<li>SQLite</li>
<li>Redshift</li>
<li>SQL</li>
<li>CockroachDB</li>
<li>MariaDB</li>
<li>Oracle</li>
</ul>
<h3>Jiffy</h3>
<p><a href="https://www.ezfuns.com/"><a href="https://www.jiffyreader.com/">Jiffy Reader</a></a>：一款能提高多数人英文阅读速度的 chrome 扩展程序，高亮单词的前缀，体验了一下确实对我有用。</p>
<h3>自由字体</h3>
<p><a href="https://ziyouziti.com/">免费的商用字体</a></p>
<h3>Fimo</h3>
<p>苹果的一款复古相机软件</p>
<h3>有知有行</h3>
<p>记账？理财？一款不错的相关<code>APP</code></p>
<h3>小宇宙</h3>
<p>一款专注于播客分享的软件，切菜必备。</p>
<h3>上百个<code>css</code>样式：linear-gradient 示例</h3>
<p><a href="https://uigradients.com/#Jupiter">uiGradients - Beautiful colored gradients</a>：或许可以找到你喜欢的那一款，并且应用到你的项目上。</p>
<h3>JavaScript 编程</h3>
<p><a href="http://bonsaiden.github.io/JavaScript-Garden/zh/">JavaScript 秘密花园</a>：一些 JavaScript 语言的知识点，探讨和分享。</p>
<h3>93 个<code>box-shadow</code>样式</h3>
<p><a href="https://getcssscan.com/css-box-shadow-examples">93 Beautiful CSS box-shadow examples - CSS Scan</a></p>
<p>拿走急用~</p>
<h3>值得学习的原生<code>CSS</code>+<code>HTML</code>小组件</h3>
<p><a href="https://uiverse.io/">CSS Buttons, checkboxes, and more! Copy paste CSS &amp; HTML</a></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[起步]]></title>
            <link>https://i5lin.top/blog/post/1516374902</link>
            <guid isPermaLink="false">1516374902</guid>
            <pubDate>Mon, 02 Jan 2023 13:02:46 GMT</pubDate>
            <description><![CDATA[看了阮一峰前辈的周刊，非常精彩。于是，自己也想记录下自己关注的一些东西，于是我的周刊起步了~

![](https://images.unsplash.com/photo-1661956602153-23384936a1d3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop]]></description>
            <content:encoded><![CDATA[<p>看了阮一峰前辈的周刊，非常精彩。于是，自己也想记录下自己关注的一些东西，于是我的周刊起步了~</p>
<p><img src="https://images.unsplash.com/photo-1661956602153-23384936a1d3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h3>临时邮件</h3>
<blockquote>
<p><a href="https://temp-mail.org/en/">Temp Mail - Disposable Temporary Email</a></p>
</blockquote>
<p>有时候只是想用一个邮件来注册一个账户，其还有配套的<code>chrome插件</code>：<a href="https://chrome.google.com/webstore/detail/temp-mail-disposable-temp/inojafojbhdpnehkhhfjalgjjobnhomj">Temp Mail - Disposable Temporary Email - Chrome 网上应用店</a>。</p>
<h4>webFont 字体压缩工具</h4>
<blockquote>
<p><a href="https://github.com/aui/font-spider">aui/font-spider: Smart webfont compression and format conversion tool</a></p>
</blockquote>
<p>在 web 开发中，有时只想让部分内容展示特定的字体，但是字体文件整体上是非常大的，使用这个库可以自动分析页面使用的 webFont 并进行按需压缩。</p>
<h3>家用树莓派架设可供外部用户访问的站点</h3>
<blockquote>
<p><a href="https://viggy28.dev/article/setting-up-ghost-in-raspberry-pi-for-free/">Home | Setting up ghost in raspberry pi for free</a></p>
</blockquote>
<p>如果你是一个 web 开发者，自己开发或者部署了一个应用在家里的设备上，可以试试参考本篇博客，利用端口隐射来实现这个需求，笔者此前见过推特上有开发者对外架设了图书分享的应用，甚至是 NAS 影视应用。</p>
<h3>Git Commit 和 emoji ❤️</h3>
<blockquote>
<p><a href="https://gitmoji.dev/">gitmoji | An emoji guide for your commit messages</a></p>
</blockquote>
<p>开发者日常为仓库添加<code>commit</code>时，添加一个让自己心情愉悦的<code>emoji</code>如何？这个应用支持安装<code>pwa</code>，我们可以轻松找到适宜当前<code>commit</code>的<code>emoji</code>。如果你是一个终端爱好者，还可以配合<a href="https://github.com/carloscuesta/gitmoji">carloscuesta/gitmoji: An emoji guide for your commit messages. 😜</a>一起食用。</p>
<h3>创建适宜的 .gitignore 文件</h3>
<blockquote>
<p><a href="https://www.toptal.com/developers/gitignore">gitignore.io - 为你的项目创建必要的 .gitignore 文件</a></p>
</blockquote>
<p>UI 界面可以搜索关键词创建初始化<code>.gitignore</code>文件，笔者比较推荐的是使用其命令行功能，官方提供了不同系统和<code>shell</code>类型的命令功能。举个例子分享：</p>
<h3>Fish</h3>
<p>终端输入：</p>
<pre><code class="language-bash">printf &quot;function gi\n\tcurl -sL https://www.toptal.com/developers/gitignore/api/\$argv\nend\n&quot; &gt; \
~/.config/fish/functions/gi.fish
</code></pre>
<p>创建一个函数文件<code>gi</code>，再在<code>fish</code>的配置文件中添加函数：</p>
<pre><code class="language-bash">function cgi --description &#39;add .gitignore file&#39;
	gi $argv &gt; .gitignore
end
</code></pre>
<p>以后就可以用<code>cgi+关键词</code>创建初始化的<code>.gitignore</code>文件了。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[转化成动力的能量更多来自于激情]]></title>
            <link>https://i5lin.top/blog/post/1552023176</link>
            <guid isPermaLink="false">1552023176</guid>
            <pubDate>Sun, 22 Jan 2023 06:49:18 GMT</pubDate>
            <description><![CDATA[转化成动力的能量更多来自于激情，而不是压力。

![](https://images.unsplash.com/photo-1617042375876-a13e36732a04?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MzB8fGNvZGV8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60)

- 严于]]></description>
            <content:encoded><![CDATA[<p>转化成动力的能量更多来自于激情，而不是压力。</p>
<p><img src="https://images.unsplash.com/photo-1617042375876-a13e36732a04?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MzB8fGNvZGV8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60" alt=""></p>
<ul>
<li>严于律己，宽以待人</li>
<li>转化成动力的能量更多来自于激情</li>
<li>Lift is short.</li>
</ul>
<h3><a href="https://github.com/wclr/yalc">wclr/yalc: Work with yarn/npm packages locally like a boss.</a></h3>
<p>让调试第三库变得简单。</p>
<p>开发第三方库的时候，可以看看这个工具。</p>
<h3><a href="https://github.com/unjs/consola#readme">GitHub - unjs/consola: Elegant Console Logger for Node.js and Browser 🐨</a></h3>
<p>现代化的<code>Javascript</code>打印工具（我就是喜欢花里胡哨），支持浏览器和<code>node</code>端。</p>
<p>好吧，优点如下：</p>
<ul>
<li>易用：接口可以随时替换原生<code>console</code>的方法</li>
<li>支持输出回调</li>
<li>支持命令行接口</li>
<li>支持多标签配置</li>
<li>支持输出中断和重启</li>
<li>支持<code>mock</code>数据</li>
</ul>
<h3><a href="https://nextui.org/">NextUI - Beautiful, fast and modern React UI Library</a></h3>
<p>React服务端渲染框架<code>Next</code>官方团队出品的<code>UI</code>库，个人觉得质量还是挺不错的，无论是学习还是在自己的项目里使用，都是一个不错的选择。</p>
<p>目前依然属于<code>beta</code>版本，但是看其进度和更新的频率还是很高效的。</p>
<p>我喜欢。</p>
<h3><a href="https://gitlab.com/gitlab-org/cli">GitLab.org / cli · GitLab</a></h3>
<p>GitHub 有<code>gh</code>命令行程序，<code>Gitlab</code>也有。</p>
<p>我一直喜欢命令行，并且认可“将一切手动处理的事情转化为程序”的观念，限于技术能力不足，但也愿意尝试坚持一步步走下去。</p>
<h3><a href="https://github.com/abhimanyu003/sttr"><a href="https://eduardoboucas.github.io/include-media/#documentation">include-media: Simple, elegant and maintainable media queries in Sass</a></a></h3>
<p><code>sass</code>语言最佳响应式设计断点语义化辅助库，即使现在<code>windicss</code>这样的库非常好用，在响应式上的支持已然足够好用，但是我依然有独立处理<code>sass</code>代码的场景，<code>include-media</code>是我的最爱。</p>
<h3><a href="https://www.hyperui.dev/">Free Open Source Tailwind CSS Components | HyperUI</a></h3>
<p>基于<code>tailwindcss</code>的开源原生组件，不要拿去就用，而是学习到它的实现方式和思维。</p>
<h3><a href="https://www.colorhexa.com/color-names">List of colors</a></h3>
<p><code>web</code>设计之语义化颜色列表。</p>
<blockquote>
<p>从<code>aliceblue</code>搜到这里，留念：</p>
<p><strong>爱丽丝蓝</strong>（英语：Alice-Blue）是一种<a href="https://zh.wikipedia.org/wiki/%E9%A1%8F%E8%89%B2">颜色</a>，是属于<a href="https://zh.wikipedia.org/wiki/Category:%E8%97%8D%E8%89%B2%E7%B3%BB">蓝色系</a>的一种颜色，其颜色接近浅蓝灰色或钢青色。</p>
</blockquote>
<h3><a href="https://www.dute.org/about">网站简介 - 独特工具箱 - dute.org</a></h3>
<p>一个免费的在线工具站点，无广告且画风简洁且具有不错的工具搜索支持。</p>
<p>支持以下分类的各种工具：</p>
<ul>
<li><p>程序开发</p>
</li>
<li><p>站长工具</p>
</li>
<li><p>图片工具</p>
</li>
<li><p>编码、解码</p>
</li>
<li><p>加密、解密</p>
</li>
<li><p>换算工具</p>
</li>
<li><p>语言处理</p>
</li>
<li><p>文本转换</p>
</li>
<li><p>数据生成</p>
</li>
<li><p>日常通用数据查询</p>
</li>
<li><p>常用对照表，提供了日常生活中方方面面的数据对照表</p>
</li>
</ul>
<h3>书籍分享</h3>
<ul>
<li><a href="https://www.bookstack.cn/books/webapi-tutorial">《阮一峰 Web API 教程》 - 书栈网 · BookStack</a>：原生<code>Web API</code>是前端开发者不可忽视的技术点。</li>
<li>《雪平锅无油料理》：单身狗最佳厨具“雪平锅”的绝配教程，照顾好自己。</li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[徒：指能力不足，又肯自愿学习的人。]]></title>
            <link>https://i5lin.top/blog/post/1552021322</link>
            <guid isPermaLink="false">1552021322</guid>
            <pubDate>Sun, 22 Jan 2023 06:37:12 GMT</pubDate>
            <description><![CDATA[此刻的我是一个能力不足，但是肯自愿学习的人。

![](https://images.unsplash.com/photo-1561145999-7e82bd65c6e2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTN8fHJvb2tpZXxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=800&q=60)

*]]></description>
            <content:encoded><![CDATA[<p>此刻的我是一个能力不足，但是肯自愿学习的人。</p>
<p><img src="https://images.unsplash.com/photo-1561145999-7e82bd65c6e2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTN8fHJvb2tpZXxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=800&q=60" alt=""></p>
<p><strong>奴：指非自愿和靠人监督的人。</strong></p>
<p><strong>徒：指能力不足，又肯自愿学习的人。</strong></p>
<p><strong>工：就是老老实实，按规矩做事的人。</strong></p>
<p><strong>匠：指精通一门技艺或手艺，靠劳动生存的人。</strong></p>
<p><strong>师：就是掌握了一些规矩，又能将规律传授给他人的人。</strong></p>
<p><strong>家：有固定的信念，让别人生活得更好的人。</strong></p>
<p><strong>圣：精通事理，通达万物，大公无私，为民立命的人</strong></p>
<h3><a href="https://www.schemecolor.com/bat-bat.php"><a href="https://neteasecloudmusicapi-docs.4everland.app/#/">网易云音乐 NodeJS 版 API</a></a></h3>
<p>macOs 不支持上传音乐到云盘,于是我借助这个库写了个<code>CLI</code>工具来上传我喜欢的音乐到我的云盘.还写了自动签到的小工具,挂在了<code>github action</code>.</p>
<p>我很喜欢音乐,后续会一直用这个库来做一些有趣的东西.</p>
<h3>Flask 超级指南</h3>
<blockquote>
<p>曾几何时我也用 python flask 写接口.</p>
</blockquote>
<p><a href="https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world">The Flask Mega-Tutorial Part I: Hello, World! - miguelgrinberg.com</a></p>
<h3>lukeed/kleur</h3>
<p><a href="https://github.com/lukeed/kleur">lukeed/kleur: The fastest Node.js library for formatting terminal text with ANSI colors~!</a></p>
<p>轻量且功能强大的<code>Node</code>终端颜色工具库,用来代替<code>chalk</code>还是不错的.</p>
<h3><a href="https://github.com/cmatsuoka/figlet">cmatsuoka/figlet: Claudio&#39;s FIGlet tree</a></h3>
<p>这个库能帮我们轻松创建<code>CLI LOGO</code>.</p>
<h3><a href="https://github.com/abhimanyu003/sttr">abhimanyu003/sttr: cross-platform, cli app to perform various operations on string</a></h3>
<p>跨平台的字符串<code>CLI</code>工具, 提供了很多实用的字符串处理功能.例如:</p>
<ul>
<li>MD5计算</li>
<li>BASE64计算</li>
<li>长度计算</li>
<li>提取信息(诸如邮件)</li>
<li>等等</li>
</ul>
<h3><a href="https://crawlee.dev/">Crawlee · Build reliable crawlers. Fast. | Crawlee</a></h3>
<p>基于<code>Nodejs</code>的网页抓取和自动化处理库,封装了无头浏览器<code>playwright</code>,简单一句话:“Python行的我也要行”</p>
<h3>这个世界的人如何表达“大笑”</h3>
<p><img src="https://cdn.beekka.com/blogimg/asset/202206/bg2022061601.webp" alt="img"></p>
<h3>Vim as IDE</h3>
<p><a href="https://github.com/ecosse3/nvim">ecosse3/nvim: A non-minimal Neovim config built to work most efficiently with Frontend Development - React &amp; Vue.js</a></p>
<p>零配置的<code>vim</code>,我决定在自己的笔记本上不再使用<code>vscode</code>, 我要转投<code>vim</code>的阵营.</p>
<p>难度?现在还是在起步阶段,虽然很久以前就接触了<code>vim</code>,但是一直没有深入尝试,没有坚持下来.这也许会是一个契机.</p>
<h3><a href="https://github.com/ts1/BLEUnlock">ts1/BLEUnlock</a></h3>
<blockquote>
<p>Lock/unlock your Mac with your iPhone, Apple Watch, or any other Bluetooth LE devices</p>
</blockquote>
<p>离开<code>pc</code>即自动锁屏,回到<code>pc</code>旁边自动激活屏幕并自动输入密码解锁的解决方案!</p>
<h3><a href="https://www.kaspersky.com.cn/resource-center/definitions/what-is-a-qr-code-how-to-scan">什么是二维码，如何扫描？</a></h3>
<p>如果你想要稍微了解一下二维码,可以花几分钟看看这篇介绍.</p>
<p><a href="https://www.gushiwen.cn/">古诗文网-古诗文经典传承</a></p>
<p>心血来潮,找了韩愈的《师说》读了读,发现这个古诗文的站点,无广告且设计具有古风气息.</p>
<p>古诗文真的很美,离开高中后再也没能再接触这些文化,可惜可惜.</p>
<p><a href="https://www.xiachufang.com/category/1009832/">【鱼汤的做法大全_鱼汤怎么做好吃_家常做法】_下厨房</a></p>
<p>见字如面, 我按着教程做了人生中自己的第一份鱼汤.(即使能接受第一次做鱼汤就失败,但实际比预料中好吃 😂)</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[所有一切经由他人之口说出来的真相，都可以被怀疑。]]></title>
            <link>https://i5lin.top/blog/post/1552019618</link>
            <guid isPermaLink="false">1552019618</guid>
            <pubDate>Sun, 22 Jan 2023 06:27:29 GMT</pubDate>
            <description><![CDATA[所有一切经由他人之口说出来的真相，都可以被怀疑。

旅行者在寺庙旁遇到三个工匠，随后询问其在做什么。其一回道：“我在搬砖”，其二则回答：“我们在砌这堵墙”。

其三看了看这寺庙，回答道：“我在修缮这间寺庙”。

这是个不知真假也不知从何而来的故事。但我们或许可以试试哪怕在某些起步阶段，做一些微不足道的事时改变思维来获取`动力`。

![](https://images.unspla]]></description>
            <content:encoded><![CDATA[<p>所有一切经由他人之口说出来的真相，都可以被怀疑。</p>
<p>旅行者在寺庙旁遇到三个工匠，随后询问其在做什么。其一回道：“我在搬砖”，其二则回答：“我们在砌这堵墙”。</p>
<p>其三看了看这寺庙，回答道：“我在修缮这间寺庙”。</p>
<p>这是个不知真假也不知从何而来的故事。但我们或许可以试试哪怕在某些起步阶段，做一些微不足道的事时改变思维来获取<code>动力</code>。</p>
<p><img src="https://images.unsplash.com/photo-1519389950473-47ba0277781c?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTF8fHdvcmt8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60" alt=""></p>
<h3><a href="https://bulma.io/">Bulma</a></h3>
<p>基于<code>Flexbox</code>的现代化纯<code>CSS</code>框架，善用此框架可以跟任何<code>JS</code>框架配合快速开发出具有良好一致性<code>UI</code>和色觉体验的应用。</p>
<blockquote>
<p>花了点时间看了它完整的文档，让我想起<code>tailwindcss</code>，确有其独到之处。</p>
</blockquote>
<p>内容很齐全，并且很<code>纯</code>。</p>
<p>配合原生JS就可以开发出不错的应用，配合自己喜欢的JS库则如虎添翼。</p>
<p>如果说<code>tailwindcss</code>是为了<code>CSS</code>而来，那么<code>Bulma</code>在<code>CSS</code>之上，还有很多组件化的思想，提供了一系列实用的组件示例。</p>
<h3><a href="https://www.reactpwa.com/">ReactPWA · An extendable boilerplate built on top of PawJS</a></h3>
<p>基于<code>React</code>和<code>PWA</code>、<code>PawJS</code>技术的项目模板，对于将要写<code>PWA</code>应用的开发者来说，很有学习价值。</p>
<h3><a href="https://smalldev.tools/">Home - SmallDev.tools</a></h3>
<p>齐全的<code>WEB</code>开发工具站，可以在这里找到日常工作中可能需要用到的技术小工具，例如<code>JSON</code>格式化、字符解码加密、日期匹配、Diff检查等等。</p>
<p>不妨看看，让其到你的收藏夹吃灰去。</p>
<h3><a href="https://openart.ai/">Discovery | OpenArt</a></h3>
<p>超过10万张由<code>DALL·E 2</code>生成的艺术图片，我想其可以作为我的某些素材来源。Blog？</p>
<h3>Fimo</h3>
<p>一个<code>IOS</code>上的相机软件，我很喜欢其复古的设计和成像效果。</p>
<h3>Plantnet</h3>
<p>一款植物识别软件，在我见到想要了解的植物时，它发挥了作用。</p>
<h3>Dime</h3>
<p>记账应用，使用后让我体会到我确实乱买了一些奇奇怪怪的东西 😂</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[伦常乖舛，立见消亡]]></title>
            <link>https://i5lin.top/blog/post/1551820079</link>
            <guid isPermaLink="false">1551820079</guid>
            <pubDate>Sat, 21 Jan 2023 14:49:11 GMT</pubDate>
            <description><![CDATA[佚名：世界上至少有两种防止“无意识作恶”的方法，一种是通过审核的方式，另一种则是信息透明的方式。

![](https://images.unsplash.com/photo-1674238924120-a9d9a0425d28?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxNHx8fGVufDB8fHx8&auto=forma]]></description>
            <content:encoded><![CDATA[<p>佚名：世界上至少有两种防止“无意识作恶”的方法，一种是通过审核的方式，另一种则是信息透明的方式。</p>
<p><img src="https://images.unsplash.com/photo-1674238924120-a9d9a0425d28?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxNHx8fGVufDB8fHx8&auto=format&fit=crop&w=800&q=60" alt=""></p>
<p>朱子治家：伦常乖舛，立见消亡。</p>
<p>Sam Parr：“当我让一个人给自己打分，他们通常说 6-9。接着我会问他们如何才能得到更高的分。这时他们会告诉你他们的弱点。”</p>
<h3>Vue 管理系统模板</h3>
<p><a href="https://github.com/vbenjs/vue-vben-admin">vbenjs/vue-vben-admin: A modern vue admin. It is based on Vue3, vite and TypeScript. It&#39;s fast！</a>：值得学习其构建方式，组件思路，整体风格。</p>
<h3>classnames 替代品</h3>
<p><a href="https://www.npmjs.com/package/clsx">clsx - npm</a>：更小且更快，用于设置前端类名。</p>
<h3>SVG 动画开发</h3>
<p><a href="https://svg-animation-booklet.vercel.app/">Home | SVG 动画开发实战</a>：值得学习的 SVG 构建动画的方案和经验分享</p>
<h3>基于 Tailwindcss 的主题插件</h3>
<p><a href="https://github.com/upupming/tailwindcss-themeable">upupming/tailwindcss-themeable: 🎨 Adds multiple themes support for Tailwind CSS and Windi CSS.</a></p>
<h3>UNSPLASH</h3>
<p><a href="https://unsplash.com/">Beautiful Free Images &amp; Pictures | Unsplash</a>：国外一个资源非常多的图片站点，提供完整的免费 API。</p>
<h3>酷站</h3>
<p><a href="https://imissmycafe.com/">I Miss My Cafe</a></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[我寻找到一个用于参考公司内部后台管理平台脚手架的Vue脚手架 vbenjs]]></title>
            <link>https://i5lin.top/blog/post/1517581529</link>
            <guid isPermaLink="false">1517581529</guid>
            <pubDate>Tue, 03 Jan 2023 15:48:54 GMT</pubDate>
            <description><![CDATA[🚀 我寻找到一个用于参考公司内部后台管理平台脚手架的Vue脚手架 vbenjs！

![](https://images.unsplash.com/photo-1488590528505-98d2b5aba04b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=29]]></description>
            <content:encoded><![CDATA[<p>🚀 我寻找到一个用于参考公司内部后台管理平台脚手架的Vue脚手架 vbenjs！</p>
<p><img src="https://images.unsplash.com/photo-1488590528505-98d2b5aba04b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h3>vue 前端管理系统示例</h3>
<p>这是笔者最近一个月见过的最好的<code>vue</code>管理系统模板:</p>
<p><a href="https://github.com/vbenjs/vue-vben-admin">vbenjs/vue-vben-admin: A modern vue admin. It is based on Vue3, vite and TypeScript. It&#39;s fast！</a></p>
<p>其配备的文档非常详细，无论你是否用过 vue 开发 web 应用，都可以阅读此项目的文档（此文档对新手非常友好，能学到非常多的系统和项目架构的知识）</p>
<h3>颜色、调色板</h3>
<p><a href="https://encycolorpedia.cn/">十六进制颜色代码表，图表及调色板 - Encycolorpedia</a>：颜色设计思路枯竭了吗，这里有你需要的所有配色搭配。</p>
<h3>私有 NPM 仓库</h3>
<p><a href="https://verdaccio.org/">A lightweight Node.js private proxy registry | Verdaccio</a>：轻量、极简、友好的私有仓库代理服务，可以为你的团队部署私有服务，利用缓存提高访问速度、发布私有库等等。</p>
<p><a href="https://github.com/nanxiaobei/resso/blob/main/README.zh-CN.md">resso/README.zh-CN.md at main · nanxiaobei/resso</a>：号称世界上最简单的 React 状态管理库，支持 React 18、React Native、SSR、小程序。</p>
<h3>Sass Mixin 库</h3>
<p><a href="https://gerillass.com/">Gerillass: The best Sass mixins library for modern web design</a>：如果你经常使用 sass 进行开发，这个 mixin 库一定让你受用无穷，在开发时给你很多很方便的功能特性支持。</p>
<h3>Vue 懒加载组件</h3>
<p><a href="https://learnvue.co/tutorials/lazy-load-components">Lazy Load Components in Vue with defineAsyncComponent</a>：图文并茂的 Vue 懒加载组件介绍。</p>
<h3>前端编码知识分享博客</h3>
<p><a href="https://codesource.io/">CodeSource.io | Top Shelf Code Training</a>：每一篇文章都值得新手朋友学习的前端知识体系博客。</p>
<h3>CSS 多行文字截断</h3>
<p><a href="https://github.com/happylindz/blog/issues/12">纯 CSS 实现多行文字截断</a></p>
<h3>纯 CSS 变量实习动态主题</h3>
<p><a href="https://www.mayashavin.com/articles/dynamic-theme-css-variables">Dynamic theme with CSS Variables</a></p>
<p>利用 CSS 变量来实现项目的动态主题切换的方案介绍（图文并茂）！</p>
<p>如果没有接触过这个知识点，看这一篇非常有用。</p>
<h3>Tailwindcss 自定义主题方案</h3>
<p><img src="https://blog.logrocket.com/wp-content/uploads/2021/08/theming-tailwindCSS.png" alt="Creating custom themes with Tailwind CSS">：</p>
<p>使用<code>Tailwind CSS</code>来创建自定义的主题，如何提高使用这个库的效率，可以看看这一篇文章。</p>
<h3>默认模块导入插件</h3>
<p><a href="https://github.com/antfu/unplugin-auto-import">antfu/unplugin-auto-import: Auto import APIs on-demand for Vite, Webpack and Rollup</a>：无论是 React 还是 Vue，使用构建工具都可以借助类似的插件实现默认模块导入，省去了我们主动导入的麻烦。主动导入的代码看起来不多，但是项目日积月累就会有非常多的显示导入的代码，懒人还是喜欢此类插件的吧~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[天干物燥，小心感染]]></title>
            <link>https://i5lin.top/blog/post/1516403611</link>
            <guid isPermaLink="false">1516403611</guid>
            <pubDate>Mon, 02 Jan 2023 13:32:34 GMT</pubDate>
            <description><![CDATA[最近朋友感染了，大家小心点。哎，真希望疫情早点过去。

![](https://images.unsplash.com/photo-1584931423298-c576fda54bd2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80)

###]]></description>
            <content:encoded><![CDATA[<p>最近朋友感染了，大家小心点。哎，真希望疫情早点过去。</p>
<p><img src="https://images.unsplash.com/photo-1584931423298-c576fda54bd2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h3>付费订阅服务</h3>
<p><a href="https://www.ezfuns.com/">介绍 | 艺爪付费</a>：为<code>APP</code>提供会员、订阅、内购付费的功能，支持的渠道非常广（个人用户一百万累计收款内免费）</p>
<h3>开源可商用的 SVG 设计系素材</h3>
<p><a href="https://undraw.co/illustrations">Illustrations | unDraw</a></p>
<h3>随机印刷排版领域的乱数假数据</h3>
<p>[Lorem Ipsum - All the facts - Lipsum generator</p>
<p><a href="https://cn.lipsum.com/"></a></p>
<h3>跨平台文件管理软件</h3>
<p><a href="https://www.spacedrive.com/">Spacedrive — A file manager from the future.</a>：非常美观，使用体验很棒（目前还只能用户自己编译）</p>
<h5>可部署的 Git 服务</h5>
<p><a href="https://github.com/theonedev/onedev">theonedev/onedev: Self-hosted Git Server with CI/CD and Kanban</a>：支持<code>CI/CD</code>和体验不错的<code>看板功能</code>!</p>
<h3>跨平台剪贴板管理 Node 类库</h3>
<p><a href="https://github.com/sindresorhus/clipboardy">sindresorhus/clipboardy: Access the system clipboard (copy/paste)</a></p>
<h3>匿名临时云盘</h3>
<p><a href="https://anonfiles.com/">Anonymous File Upload - AnonFiles</a>：20G 个人限制，下载不限速！</p>
<h3>快速部署网站</h3>
<p><a href="https://docusaurus.io/">Build optimized websites quickly, focus on your content | Docusaurus</a></p>
<h3>排版系统</h3>
<p><a href="https://type-scale.com/">Type Scale - A Visual Calculator</a>：方便地通过基准值计算页面字号体系，便于设计良好的<code>UI</code>系统</p>
<h3>YouTube 上的 Figma 入门教程</h3>
<p><a href="https://www.youtube.com/watch?v=eZJOSK4gXl4">Getting started with Figma: A beginner&#39;s guide - YouTube</a></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[去中心化的硝烟正浓，这会是我们这一代人的暴风吗？]]></title>
            <link>https://i5lin.top/blog/post/1516388303</link>
            <guid isPermaLink="false">1516388303</guid>
            <pubDate>Mon, 02 Jan 2023 13:16:32 GMT</pubDate>
            <description><![CDATA[去中心化的硝烟正浓，这会是我们这一代人的暴风吗？

![](https://images.unsplash.com/photo-1626968361222-291e74711449?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80)

### 去]]></description>
            <content:encoded><![CDATA[<p>去中心化的硝烟正浓，这会是我们这一代人的暴风吗？</p>
<p><img src="https://images.unsplash.com/photo-1626968361222-291e74711449?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h3>去中心化的电子书平台</h3>
<p><a href="https://ebook2.lorefree.com/">LoreFree-去中心化免费电子书共享社区</a>：没办法不推荐这个平台，可以在这里找到很多学习和阅读的资源。</p>
<h3>北大研究员的区块链知识课程</h3>
<p><a href="https://www.youtube.com/watch?v=UmVec9VHtpE&list=PLnTPdMjBRmAYehJkVbAXqxO-0cc9ALC6V">肖臻讲区块链技术与应用</a>：这一系列的课程分享在油管上，底部评论都挺好的，有兴趣可以看看。</p>
<h3>Rough.js</h3>
<p><a href="https://roughjs.com/">Rough.js</a>:是一个小图形库(&lt; 9kB) ，可以让你画一个粗略的、手绘的样式。该库定义了绘制直线、曲线、弧线、多边形、圆和椭圆的基元。它还支持绘制 SVG 路径。</p>
<p>感受一下效果：</p>
<p><img src="https://roughjs.com/images/cap.png" alt=""></p>
<p><img src="https://roughjs.com/images/m6.png" alt=""></p>
<p><img src="https://roughjs.com/images/m14.png" alt=""></p>
<p>使用这个工具，你得懂一些<code>canvas</code>和<code>svg</code>。</p>
<h3>Apollo</h3>
<p><a href="https://apps.apple.com/cn/app/apollo-%E8%AE%B0%E5%BD%95%E4%BD%A0%E7%9A%84%E5%BD%B1%E8%A7%86%E7%94%9F%E6%B4%BB/id1616467801">App Store 上的“Apollo-记录你的影视生活”</a>：一款 IOS 上记录影视生活的软件，你可以使用 Apollo 来给已看的电影/剧集添加评分、评论、记录自己的观影方式等，也可以用它来追踪目前在追的剧集的观看进度、或者创建自己的「待看清单」。</p>
<p>依赖 iCloud 同步，没有花哨社交，很靠谱的软件。</p>
<h3>团队协作 - Superlist</h3>
<p><a href="https://www.superlist.com/">Superlist</a>：处于 Beta 阶段的超赞团队协作应用，界面效果非常炫酷，小团队或许可以尝尝鲜。</p>
<h3>One-goal</h3>
<p><a href="https://github.com/timqian/one-goal">timqian/one-goal: A macOS menu bar APP helps you stay focused</a>：MacOS 上的一款目标提醒软件，可以在导航栏上记录下最近的工作目标，保持专注的方向。</p>
<h3>构建 Electron 开发环境</h3>
<p><a href="https://www.webtips.dev/how-to-bundle-your-react-electron-app-by-parcel">How to Bundle your React-Electron App by Parcel - Webtips</a>：介绍了如何配合 Parcel 来构建 Electron 开发环境</p>
<p>###WSL 和 Electron</p>
<p><a href="https://www.beekeeperstudio.io/blog/building-electron-windows-ubuntu-wsl2">Building an Electron App in Windows with WSL 2 and Ubuntu | Beekeeper Studio</a>：介绍如何在 windows 下利用 <code>WSL2</code> 构建和调试 Electron 应用</p>
<h3>web 开发中的字体</h3>
<p><a href="https://imweb.io/topic/5a016f791f0e50753869bfa3">自定义字体 - 腾讯 Web 前端 IMWeb 团队社区 | blog | 团队博客</a>：介绍了在<code>web</code>开发中可以实际应用到的自定义字体技术的使用示例和注意事项。</p>
<h3>字体反爬虫</h3>
<p><a href="https://juejin.cn/post/6844904014866284552">自定义字体知识整理 - 掘金</a>：自定义字体如何加密，如何防止爬虫，看看这一篇知识整理，或许对你有所帮助。</p>
<h3>公开 API 收集</h3>
<p><a href="https://apitracker.io/">Discover the best APIs and SaaS products | API Tracker</a>：收集了国外一系列的免费接口，开发者们或许可以在这里找到适合自己的免费接口。</p>
<h3>图表库</h3>
<p><a href="https://github.com/timqian/chart.xkcd">timqian/chart.xkcd: xkcd styled chart lib</a>：太强了，适用于不那么正式场合的图表数据展示，手绘风格的图让人眼前一亮。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[unsplash 最佳图片资源]]></title>
            <link>https://i5lin.top/blog/post/1516381810</link>
            <guid isPermaLink="false">1516381810</guid>
            <pubDate>Mon, 02 Jan 2023 13:09:41 GMT</pubDate>
            <description><![CDATA[图片资源、翻译软件、windows软件站、剪贴板工具 - 你值得知道的内容尽在妙才周刊

![](https://images.unsplash.com/photo-1573435567032-ff5982925350?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=]]></description>
            <content:encoded><![CDATA[<p>图片资源、翻译软件、windows软件站、剪贴板工具 - 你值得知道的内容尽在妙才周刊</p>
<p><img src="https://images.unsplash.com/photo-1573435567032-ff5982925350?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2574&q=80" alt=""></p>
<h3>图片资源网站</h3>
<p><a href="https://unsplash.com/">Beautiful Free Images &amp; Pictures | Unsplash</a>：国外的一个超级优质的图片站，这里有个人爱好者和专业的摄影师的作品，无广告可以下载超高清图片资源。其提供了官方 API 可供开发者使用，笔者的 Chrome 扩展《岚》就使用了其 API 。</p>
<h3>Windows 软件站</h3>
<p><a href="http://www.dayanzai.me/">大眼仔旭 - 专注视频剪辑、解压、录屏、思维导图等办公资源分享</a></p>
<h3>翻译软件 Bob</h3>
<p><a href="https://ripperhe.gitee.io/bob/#/">Bob</a>：小而美的翻译软件，仅供 MacOS 系统使用。</p>
<blockquote>
<p>Windows 用户可以勉强用用百度翻译桌面版</p>
</blockquote>
<h3>剪贴板管理工具</h3>
<p><a href="https://maccy.app/">Maccy - macOS clipboard manager</a>：MacOS 系统上超级好用的剪贴板管理软件，可以非常方便的查看剪贴板历史记录，如果你需要来回复制粘贴(笑)，强推。</p>
<h3>终端软件</h3>
<blockquote>
<p>各大系统都有自己默认的 terminal 软件，但是系统自带的软件最大的特点就是 UI 无特色，可定制化的内容不多，最大的优点就是稳定！如果你想试试其他类型的终端软件，以下推荐几个。</p>
</blockquote>
<ul>
<li><a href="https://iterm2.com/">iTerm2 - macOS Terminal Replacement</a> ： 知名、稳定、快速</li>
<li><a href="https://hyper.is/">Hyper™</a> ： 美</li>
<li><a href="https://tabby.sh/">Tabby - a terminal for a more modern age</a> ： 美而全，推荐</li>
<li><a href="https://alacritty.org/">Alacritty</a> ： 快！</li>
</ul>
<p>响应速度极快，支持自定义的组合键，完全本地运行，或许你可以尝试看看。</p>
<h3>编辑器</h3>
<p><a href="https://lapce.dev/">Lapce</a>：Lapce 是 Rust 语言开发的编辑器，速度非常快。但是在生态方面处于原始阶段，想用它代替成熟的编辑器还非常遥远。如果想体验体验，或者单纯使用其作为编辑器，可以试试！</p>
<h3>FFMPEG</h3>
<p><a href="https://bytefreaks.net/gnulinux/bash/ffmpeg-extract-audio-from-webm-to-mp3">ffmpeg: Extract audio from .WEBM to .MP3 – Bytefreaks.net</a>：这篇文章介绍了如何使用<code>ffmpeg</code>将<code>webm</code>转化为<code>mp3</code>资源，笔者在使用<code>youtube-dl</code>下载资源的时候手动转换格式的时候查到的，此前并未下载<code>ffmpeg</code>故无法使用内置的格式转化命令。</p>
<h3>配置文件分享</h3>
<p><a href="https://github.com/youyiqin/dotfiles-public">youyiqin/dotfiles-public: My personal dotfiles about fish / nvm / neovim / tmux</a>：日本开发者<code>devaslife</code>的个人配置分享！大赞强推大神的 YouTube 视频，我看得目瞪口呆。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[循序渐进]]></title>
            <link>https://i5lin.top/blog/post/1516378223</link>
            <guid isPermaLink="false">1516378223</guid>
            <pubDate>Mon, 02 Jan 2023 13:06:00 GMT</pubDate>
            <description><![CDATA[终端工具、素材资源、设计课程、书籍分享

![](https://images.unsplash.com/photo-1661956602868-6ae368943878?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80)

### 优质的图像素]]></description>
            <content:encoded><![CDATA[<p>终端工具、素材资源、设计课程、书籍分享</p>
<p><img src="https://images.unsplash.com/photo-1661956602868-6ae368943878?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h3>优质的图像素材资源站点 - Vecteezy</h3>
<blockquote>
<p><a href="https://www.vecteezy.com/">Download Free Vector Art, Stock Photos &amp; Stock Video Footage</a></p>
</blockquote>
<p>Vecteezy 提供免费+收费的优质矢量艺术图，注册即可可以在你的项目或应用中免费使用部分资源。</p>
<h3>前端学习资源</h3>
<p><a href="https://q.shanyue.tech/engineering/#%E8%AF%B7%E6%88%91%E5%96%9D%E6%9D%AF%E5%92%96%E5%95%A1">前端工程化知识体系</a></p>
<h3>Figma Tutorial</h3>
<blockquote>
<p>最火的设计软件</p>
</blockquote>
<p>学一点设计：<a href="https://www.figma.com/resources/learn-design/getting-started/">Learn Design with Figma</a></p>
<h3>谷歌前端入门 HTML/CSS 指南</h3>
<p><a href="https://google.github.io/styleguide/htmlcssguide.html">Google HTML/CSS Style Guide</a></p>
<h3>web 开发者大师 Dr.Axel Rauschmayer 书籍分享</h3>
<p><a href="https://exploringjs.com/">Exploring JS: JavaScript books for programmers</a></p>
<h3>终端补全可视化工具</h3>
<blockquote>
<p><a href="https://fig.io/">Fig</a></p>
</blockquote>
<p>Fig 是一个具有<code>IDE</code>风格的终端补全的开源工具，其支持目前主流的终端软件以及三大<code>shell</code>:</p>
<ul>
<li>bash</li>
<li>fish</li>
<li>zsh</li>
</ul>
<p>并且支持超过<code>300+</code>的 cli 工具补全功能，如果是你终端的重度用户，这个工具绝对不会让你失望。</p>
<p><img src="https://fig.io/images/slideshow/screenshots/scripts.png" alt=""></p>
<p>响应速度极快，支持自定义的组合键，完全本地运行，或许你可以尝试看看。</p>
<h3>前端脚手架 - nextarter-chakra</h3>
<blockquote>
<p><a href="https://github.com/sozonome/nextarter-chakra">sozonome/nextarter-chakra: battery packed template / boilerplate to initialize PWA ready Next.js app with Chakra UI &amp; Typescript setup</a></p>
</blockquote>
<p>印尼开发者<code>sozonome</code>开发的前端脚手架，支持:</p>
<ul>
<li><code>TypeScript</code></li>
<li><code>Chakra-UI</code></li>
<li><code>Next.js</code></li>
<li><code>Eslint/Pretter/husky/lint-staged</code></li>
<li><code>React</code></li>
<li><code>PWA option</code></li>
<li><code>SEO optimization/sitemap</code></li>
<li><code>framer-motion</code></li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[创业就像狂欢节游戏，而有些人只是工作人员]]></title>
            <link>https://i5lin.top/blog/post/1552111140</link>
            <guid isPermaLink="false">1552111140</guid>
            <pubDate>Sun, 22 Jan 2023 13:10:55 GMT</pubDate>
            <description><![CDATA[游乐场有一个飞镖游戏，大家纷纷尝试。但是，我买不起票来体验这种游戏，我是工作人员。]]></description>
            <content:encoded><![CDATA[<p><img src="https://images.unsplash.com/photo-1553481187-be93c21490a9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8Z2FtZXxlbnwwfDB8MHx8&auto=format&fit=crop&w=800&q=60" alt=""></p>
<h2>创业游戏</h2>
<blockquote>
<p><code>notacoward</code>:&quot;Entrepreneurship is like one of those carnival games where you throw darts or something.</p>
<p>Middle class kids can afford one throw. Most miss. A few hit the target and get a small prize. A very few hit the center bullseye and get a bigger prize. Rags to riches! The American Dream lives on.</p>
<p>Rich kids can afford many throws. If they want to, they can try over and over and over again until they hit something and feel good about themselves. Some keep going until they hit the center bullseye, then they give speeches or write blog posts about &quot;meritocracy&quot; and the salutary effects of hard work.</p>
<p>Poor kids aren&#39;t visiting the carnival. They&#39;re the ones working it.&quot;</p>
</blockquote>
<p>创业就像狂欢节游戏，你可以扔飞镖什么的。</p>
<p>中产阶级的孩子扔得起。大部分都没打中。有几个击中了目标，得到了一个小奖品。只有极少数射中了中心的靶心，获得了更大的奖励。白手起家！美国梦永存。</p>
<p>有钱人家的孩子可以买得起很多东西。如果他们愿意，他们可以一遍又一遍地尝试，直到他们得到什么东西，并对自己感觉良好。有些人一直坚持下去，直到他们成功中心靶心，然后他们发表演讲或写博客文章，谈论“精英管理”和努力工作的有益影响。</p>
<p>而有些可怜的孩子们不是来参观狂欢节的，他们才是工作人员。</p>
<h2>微信读书体验卡</h2>
<p><a href="https://weread.qnmlgb.tech/">瓦斯组队 - 无限卡 - 微信读书组队</a>：利用微信读书的组队机制，快速匹配陌生人组队即可获取不限次数的会员体验卡。</p>
<h2>李志音乐</h2>
<p><a href="https://lizhi.turkyden.com/#/">李志 BB 音乐作品播放器</a>：民间李志音乐爱好者＆web开发者开源的在线播放器，站点资源都收集自网络，并且向李志先生发了邮件告知。</p>
<p>另外，支持打包成移动APP，如果你有MacBook也可以单独下载客户端，如果你有特斯拉😂，也提供了特斯拉车载系统应用。</p>
<h2>Web 应用打包</h2>
<p><a href="https://github.com/tw93/Pake">tw93/Pake: 🤱🏻 Simply make any web page a desktop application using Rust. 🤱🏻 很简单的用 Rust 打包网页生成很小的桌面 App</a>，已有的打包应用诸如：</p>
<ul>
<li>微信读书</li>
<li>Twitter</li>
<li>李志音乐</li>
<li>YouTube</li>
<li>...</li>
</ul>
<p>开发者可以自己定制自己的需求，小白也可以去下载某些APP，如果还没有打包也可以发起请求。</p>
<h2>安全的RM命令</h2>
<p><a href="https://github.com/nivekuil/rip">nivekuil/rip: A safe and ergonomic alternative to rm</a>：使用<code>Rust</code>写的命令行程序，更快更安全的删除文件或目录的程序，并且支持误删除恢复。</p>
<h2>克隆CP命令</h2>
<p><a href="https://github.com/tarka/xcp">tarka/xcp: An extended `cp`</a>：依然是<code>Rust</code>重写的命令行程序，克隆了系统的<code>cp</code>命令，更快且具有以下功能：</p>
<ul>
<li>展示进度条</li>
<li>支持<code>.gitignore</code>限制不需要复制的文件</li>
</ul>
<p>并且由于底层调用系统机制的优化，其复制大文件更快，我们可以利用<code>alias</code>直接替换掉<code>cp</code>命令。</p>
<h2>JS全文搜索库</h2>
<p><a href="https://lucaong.github.io/minisearch/">MiniSearch</a>：这是一个小而美的全文搜索库，可以在浏览器和<code>Node</code>环境下运行，如果你将要使用JS技术栈去做一些全文搜索，可以找到这个库。</p>
<h2>输入法应用匹配</h2>
<p><a href="https://inputsource.pro/zh-CN?utm_medium=email&utm_source=Revue%20newsletter&utm_campaign=%E5%8F%AF%E4%B9%90%E5%91%A8%E6%8A%A5">Input Source Pro - 优化 macOS 输入法使用体验</a>：切换不同的应用时，自动切换为预设的输入法。</p>
<h2>安全的RM命令</h2>
<p><a href="https://github.com/nivekuil/rip">nivekuil/rip: A safe and ergonomic alternative to rm</a>：使用<code>Rust</code>写的命令行程序，更快更安全的删除文件或目录的程序，并且支持误删除恢复。</p>
<h2>克隆CP命令</h2>
<p><a href="https://github.com/tarka/xcp">tarka/xcp: An extended `cp`</a>：依然是<code>Rust</code>重写的命令行程序，克隆了系统的<code>cp</code>命令，更快且具有以下功能：</p>
<ul>
<li>展示进度条</li>
<li>支持<code>.gitignore</code>限制不需要复制的文件</li>
</ul>
<p>并且由于底层调用系统机制的优化，其复制大文件更快，我们可以利用<code>alias</code>直接替换掉<code>cp</code>命令。</p>
<h2>JS全文搜索库</h2>
<p><a href="https://lucaong.github.io/minisearch/">MiniSearch</a>：这是一个小而美的全文搜索库，可以在浏览器和<code>Node</code>环境下运行，如果你将要使用JS技术栈去做一些全文搜索，可以找到这个库。</p>
<h2>输入法应用匹配</h2>
<p><a href="https://inputsource.pro/zh-CN?utm_medium=email&utm_source=Revue%20newsletter&utm_campaign=%E5%8F%AF%E4%B9%90%E5%91%A8%E6%8A%A5">Input Source Pro - 优化 macOS 输入法使用体验</a>：切换不同的应用时，自动切换为预设的输入法。</p>
<h2>世界国旗资源</h2>
<p><a href="https://seekflag.com/">Seek Flag | Flags of the World</a>：免费下载全世界的国家的国旗，提供<code>png</code>和<code>svg</code>格式。</p>
<h2>CSS 迷你课程</h2>
<p><a href="https://doc.clickup.com/37465296/d/13qb6g-64/css-mini-course/13qb6g-84/css-mini-course">CSS Mini Course</a>，前提是你能上推特。</p>
<h2>油管私有接口</h2>
<p><a href="https://www.npmjs.com/package/youtubei.js">youtubei.js - npm</a>：如果有一天，你想利用油管的数据去做一些有趣的事情，可能这个库能帮到很多忙。</p>
<h2>新世代企业论坛</h2>
<p><a href="https://www.trickle.so/">Trickle – A better way to align your team</a>：新世代的人或许会喜欢，定位是企业内的员工可以工作和交流，分享大家的想法，设计和交互非常现代化。</p>
<h2>制作猫的照片</h2>
<p><a href="https://uchinoko-maker.jp/">【公式】うちのこメーカー | 猫イラストを無料で簡単作成！</a></p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Hello, Ruby]]></title>
            <link>https://i5lin.top/blog/post/1559910415</link>
            <guid isPermaLink="false">1559910415</guid>
            <pubDate>Fri, 27 Jan 2023 15:02:16 GMT</pubDate>
            <description><![CDATA[Hello, Ruby! 学习 ruby 的语法知识，并且做一个 ruby 项目。]]></description>
            <content:encoded><![CDATA[<p>Hello, Ruby!</p>
<p><img src="https://mllmu3qmmprr.i.optimole.com/w:auto/h:auto/q:mauto/f:avif/https://www.codingclub.org/wp-content/uploads/elementor/thumbs/ruby-pnq82bm8hpfdnwihsoudyp39okf9fczjwsxjkewd54.jpg" alt=""></p>
<h1>Ruby Programming</h1>
<blockquote>
<p><a href="https://www.techotopia.com/index.php/Ruby_Essentials"><a href="https://www.techotopia.com/index.php/Ruby_Essentials">Ruby Essentials - Techotopia</a></a></p>
</blockquote>
<h2>HELLO WORLD.Ruby.</h2>
<pre><code class="language-ruby">puts &#39;hello world,ruby.&#39;
</code></pre>
<h3>Strings,Numbers,Classes and Objects</h3>
<pre><code class="language-ruby">puts(&#39;Enter your name:&#39;)
name = gets()
puts(&#39;hello, #{name}&#39;)
</code></pre>
<p><code>Puts</code> string into terminal.Gets input from terminal and save to variable <em>&quot;name&quot;</em> , and puts string with variable.</p>
<blockquote>
<p>If your do not want to auto add a linefeed after output, you can use <code>print</code> instead of <code>puts</code>.</p>
</blockquote>
<p>Ruby is case sensitive,<code>myvar</code> and <code>myVar</code> is difference variable.</p>
<p>The funny thing is, <code>#{name}</code>(called embedded evaluation) only works with double quotes and neither a single quote.</p>
<p>Next example about numbers:</p>
<pre><code class="language-ruby">subtotal = 100.00
taxrate = 0.175
tax = subtotal * taxrate
puts &quot;Tax on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}&quot;
</code></pre>
<p>or other version:</p>
<pre><code class="language-ruby">taxrate = 0.175
print &quot;Enter price (ex tax): &quot;
s = gets
subtotal = s.to_f
tax = subtotal * taxrate
puts &quot;Tax on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}&quot;
# this is a comment

=begin
This is a
multiline
comment
=end
</code></pre>
<blockquote>
<p>You can do not use <code>()</code> after any method, but sometimes ruby intercepted will warn you.</p>
</blockquote>
<p>Here <code>s.to_f</code> is a method of the String class.It attempts to convert the string to a floating point number.</p>
<p>If the string cannot be converted, <code>0.0</code> is returned.</p>
<p>You can find two comment at above code.</p>
<h3>Condition</h3>
<p>Example:</p>
<pre><code class="language-ruby">taxrate = 0.175
print &quot;Enter price (ex tax): &quot;
s = gets
subtotal = s.to_f
if (subtotal &lt; 0.0) then
	subtotal = 0.0
end
tax = subtotal * taxrate
puts &quot;Tax on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}&quot;
</code></pre>
<p>Condition is simple like those:<code>if (...) then ... end</code>.</p>
<p>In fact, the brackets are optional, also the keyword <code>then</code> is optional too.But if you were to write the following, with no line break after the test condition, them <code>then</code> would be obligatory:</p>
<p><code>if( subtotal&lt; 0.0 ) then subtotal = 0.0 end</code></p>
<h3>Local and global variables</h3>
<p>Variables such as these, which begin with a lowercase character, are local variables, otherwise,   is a global variable.Look this example:</p>
<pre><code class="language-ruby">localvar = &quot;hello&quot;
$globalvar = &quot;goodbye&quot;
def aMethod
  localvar = 10
  puts( localvar )
  puts( $globalvar )
end
def anotherMethod
  localvar = 500
  $globalvar = &quot;bonjour&quot;
  puts( localvar )
  puts( $globalvar )
end
aMethod # echo 10,goodbye
anotherMethod # echo 500, bonjour
puts localvar # echo hello (scope variable)
puts $globalvar # echo boujour (global variable)
</code></pre>
<h3>Classes and Objects</h3>
<blockquote>
<p>A &#39;class&#39; is the blueprint for an object.And Ruby is object oriented.</p>
</blockquote>
<p>Let us create a dog class:</p>
<pre><code class="language-ruby">class Dog
  def set_name(aName)
    @myname = aName
  end
end
</code></pre>
<p>The class definition begins with the keyword <code>class</code>(all lowercase) and the name of the class itself, which must begin with an uppercase letter.</p>
<p>The class contains a method <code>set_name</code>.This takes a incoming argument, <code>aName</code>.</p>
<p>The body of the method assigns the value of  <code>aName</code> to variable called <code>@myname</code>.</p>
<h3>Instance variables</h3>
<p>Variables beginning with the <code>@</code> charactor are <code>instance variable</code>,that means that they belong to individuals objects, or instance of the class.</p>
<pre><code class="language-ruby">myDog = Dog.new
yourDog = Dog.new
myDog.set_name(&#39;Aaron&#39;)
yourDog.set_name(&#39;Jay&#39;)
</code></pre>
<p>At the moment, these two dogs have a difference name between other.The data inside each object is private.</p>
<p>This is called <code>data hiding</code>.</p>
<p>Now, we need each dog to know its own name.</p>
<pre><code class="language-ruby">def getName
  return @myname
end
</code></pre>
<blockquote>
<p>The <code>return</code> keyword here is optional.When it is omitted, Ruby methods will return the last expression evaluated.But for the sake of clarity, I want to keep write a clarity return value which I plan to use.</p>
</blockquote>
<h3>Messages,Methods and Polymorphism</h3>
<p>Nothing.</p>
<h3>Constructors - new and initialize</h3>
<p>Example:</p>
<pre><code class="language-ruby">class Demo
  def initialize(name, descrip)
    @name = name
    @descrp = descrip
  end
end
</code></pre>
<p>When a class contains  a method named <code>initialize</code> this will be automatically called when an object is created use the new method.</p>
<p>Developers often use a <code>initialize</code> method to set the values of an object&#39;s instance variables.</p>
<h3>Inspecting Objects</h3>
<p>The <code>inspect</code> method is defined for all Ruby Objects.It returns a string containing a humen-readable representation of the object.</p>
<p>Ruby also provides the <code>p</code> method as a shortcut to inspecting object</p>
<h2>Class Hierarchies,Attributes and Class variables</h2>
<h3>Class Hierarchies</h3>
<pre><code class="language-ruby">class Thing
  def initialize(name, des)
    @name = name
    @des = des
  end
  
  def getName
    return @name
  end
  
  def setName(name)
    @name = name
  end
end

class Treasure  &lt; Thing
  def initialize(name, des, value)
    super(name, des) # super =&gt; passes name , des and value to super class&#39;s initialize function
    # super() =&gt; no pass params
    @value = value
  end
  
  def value
    return @value
  end
  
  def value=(v)
    @value = v
  end
end
</code></pre>
<p>Classes have own <code>set</code> accessors, you can not use space between set accessors code.</p>
<p>So this is correct:</p>
<p><code>def name=(name)</code></p>
<p>And, this is an error:</p>
<p><code>def name = (name)</code></p>
<p>But, we have a shortcut method:</p>
<pre><code class="language-ruby">class Dog
  attr_reader :name
  attr_writer :name
  # more terse: attr_accessor :name
end
</code></pre>
<p>Now, you can set and get dog instance&#39;s name easily.</p>
<p>If you want to declare multi accessor, just like this:</p>
<pre><code class="language-ruby">class Dog
  attr_reader :name, :id
  attr_writer(:name, :id)
  # or 
  # attr_accessor(:name, :id)
end
</code></pre>
<blockquote>
<p>As always, in Ruby, brackets around the arguments are optional but, in my view (for reasons of clarity), are to be preferred.</p>
</blockquote>
<h3>Calling methods of a superclass</h3>
<p>If you want to init some value from superclass,you need to call super method.</p>
<h3>Class variables</h3>
<p>The two <code>@</code> characters at the start pf this variable name, <code>@@num</code>, define this to be a <code>class variable</code>.</p>
<p>if superclass define a class variable, then all subclass can share this variable.</p>
<h3>CONSTANTS INSIDE CLASSES</h3>
<pre><code class="language-ruby">class X
  A = 10
  class Y
  end
end
</code></pre>
<p>If you want to access the constant <code>A</code>, you must to use the special scope resolution operator  <code>::</code> like this:</p>
<p><code>X::A</code> or <code>ob = X::Y.new</code>!</p>
<h3>Partial classess</h3>
<p>In Ruby its is not obligatory to define a class all in one place.</p>
<pre><code class="language-ruby">class A
  def a
  puts( &quot;a&quot; )
  end
end

class B &lt; A
  def ba1
  	puts( &quot;ba1&quot; )
  end
end

class A
  def b
  puts( &quot;b&quot; )
  end
end

class B &lt; A
  def ba2
  	puts( &quot;ba2&quot; )
  end
end
</code></pre>
<p>Now,if I create a B object, all the methods of both A and B are available to it:</p>
<pre><code class="language-ruby">ob = B.new
ob.a # &#39;a&#39;
ob.b # &#39;b&#39;
ob.ba1 # &#39;ba1&#39;
ob.ba2 # &#39;ba2&#39;
</code></pre>
<p>You can use partial class definitions to add features onto Ruby&#39;s standard class such as Array:</p>
<pre><code class="language-ruby">class Array
  def hello
    puts &quot;hello&quot;
  end
end

[1,2,3].hello # &#39;hello&#39;
</code></pre>
<h3>User-Defined String Delimiters</h3>
<p>%Q/This is the same as a double-quoted string./
%/This is also the same as a double-quoted string./
%q/And this is the same as a single-quoted string/</p>
<p>Also, you can define your self string delimiters like this:</p>
<pre><code class="language-ruby">%Q[This is a string] # as &quot;this is a string&quot;
</code></pre>
<h3>BACKQUOTES</h3>
<p>Blackquotes means execute a system command.</p>
<pre><code class="language-ruby">puts(`ls`) # just like exec ls command on unix system
print(&quot;show dir: #{%x{ls}}&quot;)
</code></pre>
<h2>String Handling</h2>
<h3>Concatenation</h3>
<pre><code class="language-ruby">s = &quot;hi&quot; &lt;&lt; &quot;yo&quot;
s = &quot;hi&quot; + &quot;yo&quot;
s = &quot;hi&quot; &quot;yo&quot; 
# all echo: &#39;hiyo&#39;
</code></pre>
<blockquote>
<p>Puts a array will put each item in a newline,prints not.</p>
</blockquote>
<h3>String Assignment</h3>
<pre><code class="language-ruby">s = &quot;hello&quot;
s[1] # prints out index 1 =&gt; e
s[-1] # o
s[1,2] # el
</code></pre>
<p>If you want to remove some charactor from a string.</p>
<pre><code class="language-ruby">s = &quot;hello&quot;
s.chop # hell
s = &quot;hello
&quot;
s.chop # hello

s.chomp # just remove record separator , echo hello
s.chomp(&#39;lo&#39;) # hel
</code></pre>
<h3>Format String</h3>
<p>Ruby provides the <code>printf</code> method to print <code>format strings</code> containing specifiers starting with a percent sign, <code>%</code>.</p>
<pre><code class="language-ruby">printf(&quot;%0.02f&quot;, 10.123) # 10.12
</code></pre>
<h3>Ranges</h3>
<pre><code class="language-ruby">a = (1..10)
a.to_A # [1,2,3,...,10]
b = (&#39;b&#39;..&#39;e&#39;)
b.to_a # [&#39;b&#39;,&#39;c&#39;,&#39;d&#39;,&#39;e&#39;]
</code></pre>
<h3>Interation with range</h3>
<pre><code class="language-ruby">for i in (1..3) do
  puts(i)
end
# 1,2,3 with newline
</code></pre>
<h3>Heredocs</h3>
<pre><code class="language-ruby">str = &lt;&lt;EODOC
I am Jay.
Yo
EODOC
</code></pre>
<h2>Arrays and Hashes</h2>
<p>An Array is a sequential collection of items in which each item can be indexed.</p>
<p>In an array, each item can be difference type.</p>
<h3>Create Arrays</h3>
<p>In common with many other programming languages, Ruby uses square brackets to delimit an array.</p>
<pre><code class="language-ruby">arr = [&#39;one&#39;, &#39;two&#39;]
puts(arr[1]) # two
Array.new # []
Array.new(2) # [nil, nil]
Array.new(2, &#39;a&#39;) # [&#39;a&#39;, &#39;a&#39;]
</code></pre>
<h3>Iteration over arrays</h3>
<pre><code class="language-ruby">arr = [1,2,3]
for i in arr
  puts(i.inspect)
end
# 1
# 2
# 3
</code></pre>
<h3>Indexing into array</h3>
<pre><code class="language-ruby">arr = [&#39;h&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;]
arr[0,5] # [&#39;h&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;]
arr[0..1] # [&#39;h&#39;]
arr = []
a[0] = 1
a[3] = 3
print(a) # [1, nil, 3]
</code></pre>
<h3>Copy arrays</h3>
<pre><code class="language-ruby">arr1 = [&#39;h&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;]
arr2 = arr1
arr2[0] = &#39;x&#39;
arr1 # [&#39;x&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;]
arr3 = arr1.clone
# arr3 is independent
</code></pre>
<h3>Sorting arrays</h3>
<pre><code class="language-ruby">arr.sort{
  |a,b|,
  a.to_s &lt;=&gt; b.to_s # if a &gt; b, return 1,if a === b,return 0, else return -1
  }
</code></pre>
<h3>Comparing values</h3>
<p>The comparison <code>operator</code> &lt;=&gt; (which is, in fact, a method) is defined in the Ruby module named Comparable.</p>
<p>Other operator:</p>
<ul>
<li><code>&lt;</code>: less than</li>
<li><code>==</code> : equal to</li>
<li><code>&gt;</code>: lass than</li>
</ul>
<h3>Array methods</h3>
<ul>
<li>flatten</li>
<li>compact</li>
<li>&lt;&lt;</li>
<li>clear</li>
<li>delete</li>
<li>delete_at</li>
<li>...</li>
</ul>
<blockquote>
<p>Visit here: <a href="https://www.freecodecamp.org/news/common-array-methods-in-ruby/"><a href="https://www.freecodecamp.org/news/common-array-methods-in-ruby/">Most Common Ruby Array Methods You Should Know</a></a></p>
</blockquote>
<h3>Creating Hashes</h3>
<pre><code class="language-ruby">h1 = Hash.new
h2 = Hash.new(&quot;some kind of ring&quot;)
h1 # {}
h1[&#39;name&#39;] = &#39;Jay&#39;
h1 # {&#39;name&#39;: &#39;Jay&#39;}
</code></pre>
<p>Hash map&#39;s key can be any type in principle.</p>
<h3>Copying a hash</h3>
<pre><code class="language-ruby">h1 = Hash.new
h1[&#39;a&#39;] = &#39;a&#39;
h2 = h1.clone
h2[&#39;a&#39;] = &#39;aa&#39;
h1[&#39;a&#39;] # &#39;a&#39;
</code></pre>
<h3>Sorting a hash</h3>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Vue3 - 101 挑战]]></title>
            <link>https://i5lin.top/blog/post/1615400960</link>
            <guid isPermaLink="false">1615400960</guid>
            <pubDate>Wed, 08 Mar 2023 14:48:12 GMT</pubDate>
            <description><![CDATA[这几天晚上，我打算花点时间直接通关 Vuejs 挑战。]]></description>
            <content:encoded><![CDATA[<p><img src="https://user-images.githubusercontent.com/23721611/223744407-a1ee28c4-e490-470c-93b1-5b7549113d23.png" alt="image"></p>
<p>感谢<a href="https://cn-vuejs-challenges.netlify.app/questions/10-lifecycle/README.zh-CN.html">Vue.js挑战 | Vue.js挑战</a>系列文章，本文是笔者自己进行<code>vue</code>挑战的知识总结。推荐在阅读时配合<a href="https://vuejs.org/guide/introduction.html">Introduction | Vue.js</a>“食用”！</p>
<blockquote>
<p>PS：本文全文使用<code>typescript</code>和<code>vue3 options api with setup syntax suger</code>!</p>
</blockquote>
<h2>内置 API 挑战</h2>
<h3>DOM 传送门</h3>
<p>vue 提供了<code>Teleport</code>组件来将组件内的一部分模板渲染到指定的<code>DOM</code>节点下面（即渲染到此组件之外）。</p>
<p>这个组件最常见的需求就是模态框，动态的模态框从整个应用的视角下来看，应该是某种场景下被触发而产生的，并且为了让 CSS 层级更为清晰或易于控制其样式免受父元素影响，最佳实践应该是将之渲染到 body 或某些特殊节点下会更好。</p>
<p>看示例：</p>
<pre><code class="language-vue">&lt;button @click=&quot;open = true&quot;&gt;Open Modal&lt;/button&gt;

&lt;Teleport to=&quot;body&quot;&gt;
  &lt;div v-if=&quot;open&quot; class=&quot;modal&quot;&gt;
    &lt;p&gt;Hello from the modal!&lt;/p&gt;
    &lt;button @click=&quot;open = false&quot;&gt;Close&lt;/button&gt;
  &lt;/div&gt;
&lt;/Teleport&gt;
</code></pre>
<p>通常，给<code>to</code>一个元素选择器即可，并且还支持<code>disabled</code>属性来控制某些场景下的特殊需求，例如在移动端将之视为普通的内部组件来使用，如此一来可以减少一些额外的逻辑判断代码。</p>
<p>最后，推荐为<code>teleport</code>设置一个固定的目标元素而不是使用<code>body</code>，因为在很多场景下动态地追加元素到<code>body</code>下可能会产生一些意想不到的变动，例如 SSR 服务端渲染的情况下，通常 body 就会包含一些服务端渲染的代码，从而让<code>teleport</code>难以确定激活的正确位置。</p>
<h3>性能优化的指令</h3>
<p>如果你有一个仅渲染一次的组件或元素，则可以为之添加<code>v-once</code>指令，从而让 vue 在更具数据更新重新渲染的时候跳过这些元素。</p>
<p>说人话就是，如果你需要在某个地方渲染一个响应式数据，却不希望其数据变更后重新渲染这个地方，则可以添加<code>v-once</code>指令。举例：在某个位置同时渲染初始值和动态初始值。</p>
<p>此外，在<code>vue3.2</code>还新增了一个<code>v-memo</code>指令：</p>
<pre><code class="language-vue">&lt;div v-memo=&quot;[valueA, valueB]&quot;&gt;
  ...
&lt;/div&gt;
</code></pre>
<p>在组件渲染时当且仅当<code>valueA</code>或<code>valueB</code>都不变时，其内部的渲染将被跳过。这个场景其实比较少，如果你有超过<code>1000</code>的<code>v-for</code>循环，则可以使用这个机制进行渲染优化，否则这个渲染就没多大必要了，毕竟缓存条件对比也需要消耗资源。</p>
<h2>CSS 属性</h2>
<h3>动态 CSS</h3>
<p>如果你想使用一个响应式的变量去控制某个 DOM 的 CSS 属性，通常有两种简单的方式：</p>
<ol>
<li>给节点添加<code>style</code>，其值使用变量来替换</li>
<li>在 style 模板选择器中使用<code>v-bind(variable)</code>来设置属性</li>
</ol>
<p>代码示例：</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { ref } from &#39;vue&#39;;
const theme = ref(&#39;red&#39;);
const px = ref(40);

const colors = [&#39;blue&#39;, &#39;yellow&#39;, &#39;red&#39;, &#39;green&#39;];

setInterval(() =&gt; {
  theme.value = colors[Math.floor(Math.random() * 4)];
}, 1000);
&lt;/script&gt;

&lt;template&gt;
  &lt;p&gt;hello&lt;/p&gt;
&lt;/template&gt;

&lt;style scoped&gt;
p {
  color: v-bind(theme);
  font-size: v-bind(&#39;&quot;100px&quot;&#39;);
  width: v-bind(`${px}px`);
}
&lt;/style&gt;
</code></pre>
<p><code>v-bind</code>函数内是一个表达式（需要用单引号包起来），<code>theme</code>还能自动解构，如果你需要设置一些单位则可以使用引号。</p>
<h3>常规 CSS</h3>
<p>在<code>vue</code>组件中，局部样式和全局样式示例如下：</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;p&gt;Hello Vue.js&lt;/p&gt;
&lt;/template&gt;

&lt;style scoped&gt;
p {
  font-size: 20px;
  color: red;
  text-align: center;
  line-height: 50px;
}

:deep(body) {
  width: 100vw;
  height: 100vh;
  background-color: burlywood;
}

:global(body) {
  width: 100vw;
  height: 100vh;
  background-color: burlywood;
}
  
:slotted(div) {
  color: blue;
}
&lt;/style&gt;
</code></pre>
<p><code>vue</code>文件最终将编译成<code>js</code>和<code>css</code>，在上面的例子中<code>style</code>加上了<code>scoped</code>标志会让<code>vue-loader</code>在编译的时候给<code>html</code>标签添加随机的<code>data-v-</code>自定义属性来配合<code>选择器</code>进行组件之间的样式隔离。</p>
<p>此外，还可以使用<code>:global</code>伪类来实现全局样式设置，使用<code>:deep()</code>伪类来影响更深处的子组件样式。由于默认样式不会影响插槽内的内容，因此可以使用<code>:slotted()</code>插槽选择器来设置传入插槽的样式。</p>
<blockquote>
<p>标签结合 scoped 限制作用域时的效率相对类名或 id 来说会慢许多倍（来自官方文档的解释），此外组件还需要小心引用递归现象。 </p>
</blockquote>
<p>此外，使用<code>v-html</code>动态生成的内容不受<code>scoped</code>影响，此时可以使用<code>:deep()</code>选择器来设置样式，亦或使用单独的样式表。</p>
<h3>CSS Module</h3>
<p>使用<code>scoped</code>属性会在<code>html</code>的标签中生成用于样式隔离的<code>data-v-</code>自定义属性，如果真的不想要这些自定义属性，则可以考虑<code>CSS Module</code>方案，这里就不深入讨论了。</p>
<h2>组件</h2>
<h3>Prop 验证</h3>
<p>组件需要显式申明其接受的<code>prop</code>，如此一来便可以于透传的属性进行区分。</p>
<p>prop 需要注意的是推荐和社区保持一致，使用<code>kebad-case</code>语法，如此一来可以和 html 属性的书写形式保持一致。</p>
<p><code>prop</code>可以传递不同类型的值，当传递的<code>prop</code>是<code>true</code>时，推荐直接写此属性即可，可以让代码稍微简洁一些。</p>
<p>可以使用一个变量绑定多个<code>prop</code>:</p>
<pre><code class="language-vue">export default {
  data() {
    return {
      post: {
        id: 1,
        title: &#39;My Journey with Vue&#39;
      }
    }
  }
}

// 传递
&lt;BlogPost v-bind=&quot;post&quot; /&gt;
// 等同于
&lt;BlogPost :id=&quot;post.id&quot; :title=&quot;post.title&quot; /&gt;
</code></pre>
<p>单项数据流是<code>vue</code>组件使用的最佳实践，不要去改动对象或数组类型的<code>prop</code>，即使你可以做到。</p>
<p>通常，我们可以写<code>prop</code>类型检验来提高开发效率，如果你使用 JavaScript 开发 vue 程序，可以参考文档来编写类型检查：<a href="https://cn.vuejs.org/guide/components/props.html#one-way-data-flow">Props | Vue.js</a>！</p>
<p>而如果你像我一样使用<code>TypeScript</code>构建应用，则可以参考如下示例：</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot;&gt;
  import { type PropType } from &#39;vue&#39;
  withDefaults(defineProps&lt;{
    title?: string
    likes: number,
    labels?: string[]
  }&gt;(), {
    // here we have default values
    title: &#39;---&#39;,
    labels: () =&gt; [&#39;one&#39;, &#39;two&#39;]
  })
&lt;/script&gt;
</code></pre>
<p>对于原始数据类型可以直接使用<code>withDefaults</code>函数的第二个参数传递值，而对象则在传值时使用返回其正确类型的函数。</p>
<h3>函数式组件</h3>
<p>有时候你可能会需要一个函数式的组件：</p>
<pre><code class="language-vue">import { h } from &quot;vue&quot;;

const DynamicHeading = (props, context) =&gt; {
  return h(`h${props.level}`, context.attrs, context.slots);
};
</code></pre>
<p>其用法如下：</p>
<pre><code class="language-vue">&lt;dynamic-heading :level=&quot;1&quot;&gt;Hello&lt;/dynamic-heading&gt;
&lt;dynamic-heading :level=&quot;2&quot;&gt;World&lt;/dynamic-heading&gt;
</code></pre>
<p>函数式组件的传入参数：<code>props</code>和<code>context</code>，其中<code>context</code>包括：<code>attrs/slots/emit</code>属性。其实现最终是返回一个 <code>h()</code>函数创建的<code>vnode</code>!</p>
<p>上述代码是一个动态标题的函数式组件，根据传入的<code>props.level</code>来决定渲染的是<code>h1</code>还是<code>h2</code>等，函数式组件在某些场景比传统组件更易用，甚至更易于组织代码。传统组件一个组件即一个文件，而函数式组件可以将多个功能性的组件放在同一个文件。</p>
<h3>树组件</h3>
<p>树组件的需求即得知其数据格式是树，对于树这种结构我们通常可以考虑递归的方法去遍历数据。</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot;&gt;
interface TreeData {
  key: string;
  title: string;
  children: TreeData[];
}
defineProps&lt;{ data: TreeData[] }&gt;();
&lt;/script&gt;

&lt;template&gt;
  &lt;ul&gt;
    &lt;li v-for=&quot;{ key, title, children } in data&quot; :key=&quot;key&quot;&gt;
      &lt;span&gt;{{ title }}&lt;/span&gt;
      &lt;TreeComponent v-if=&quot;children&quot; :data=&quot;children&quot;&gt;&lt;/TreeComponent&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/template&gt;
</code></pre>
<p>上述代码文件为：<code>TreeComponent.vue</code>，在模板内部可以使用自身来实现递归。</p>
<h2>可组合函数</h2>
<p>vue composable function 指的是用 vue 的 composition API 来封装和复用具有<code>逻辑状态</code>的函数。</p>
<p>举个切换器的例子：</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot;&gt;
import { ref } from &#39;vue&#39;;
/**
 * Implement a composable function that toggles the state
 * Make the function work correctly
 */
function useToggle(init: boolean) {
  const state = ref(init);
  const toggle = () =&gt; (state.value = !state.value);
  return [state, toggle];
}

const [state, toggle] = useToggle(false);
&lt;/script&gt;

&lt;template&gt;
  &lt;p&gt;State: {{ state ? &#39;ON&#39; : &#39;OFF&#39; }}&lt;/p&gt;
  &lt;p @click=&quot;toggle&quot;&gt;Toggle state&lt;/p&gt;
&lt;/template&gt;
</code></pre>
<p>编写一个<code>useToggle</code>函数来快速创建可复用的<code>state</code>和翻转其状态的函数，这个功能可以方便地在很多地方复用。并且因为这是一个纯函数，我们可以方便地扩展和测试。</p>
<p>再举个计数器的例子：</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot;&gt;
import { ref } from &#39;vue&#39;;
interface UseCounterOptions {
  min?: number;
  max?: number;
}

/**
 * Implement the composable function
 * Make sure the function works correctly
 */
function useCounter(initialValue = 0, options: UseCounterOptions = {}) {
  const count = ref(initialValue);

  const inc = () =&gt; {
    if (count.value === options?.max) {
      return;
    }
    count.value += 1;
  };
  const dec = () =&gt; {
    if (count.value === options?.min) {
      return;
    }
    count.value -= 1;
  };
  const reset = () =&gt; (count.value = initialValue);
  return { count, inc, dec, reset };
}

const { count, inc, dec, reset } = useCounter(0, { min: 0, max: 10 });
&lt;/script&gt;

&lt;template&gt;
  &lt;p&gt;Count: {{ count }}&lt;/p&gt;
  &lt;button @click=&quot;inc&quot;&gt;inc&lt;/button&gt;
  &lt;button @click=&quot;dec&quot;&gt;dec&lt;/button&gt;
  &lt;button @click=&quot;reset&quot;&gt;reset&lt;/button&gt;
&lt;/template&gt;
</code></pre>
<h2>组合式 API - Composition API</h2>
<h3>生命周期钩子</h3>
<p>这一题的问题在于收起子组件之后，全局的定时器任务没有被清除，因此再次挂载渲染子组件之后会再次执行一个新的定时任务，二者会影响到<code>count</code>值。</p>
<p>因此，问题的关键在于：我们需要在卸载子组件的时候清除定时器任务。</p>
<p>关于组件的生命周期，可以看看这个图示：</p>
<p><img src="https://vuejs.org/assets/lifecycle.16e4c08e.png" alt=""></p>
<blockquote>
<p>查看文档：<a href="https://vuejs.org/api/composition-api-lifecycle.html">Composition API: Lifecycle Hooks | Vue.js</a></p>
</blockquote>
<p>因此，我们可以使用<code>onUnmounted</code>生命周期函数，传入一个回调函数，在这个函数中清理掉定时任务即可！</p>
<h3>Ref - 你应该知道的知识</h3>
<p><code>ref</code>是响应式 API 最常见的知识之一，接受一个内部值，返回一个响应式的、可更改的 ref 对象，此对象只有一个指向其内部值的属性 <code>.value</code>。它可以在我们组件挂载之后获取一个特定<code>DOM</code>元素或子组件实例的直接引用。<code>ref</code>也是一个可以修改的响应式对象，并且可以追踪和触发相关的副作用。</p>
<p>在<code>TypeScript</code>中，要为<code>ref</code>标注类型可以从<code>vue</code>导出类型<code>Ref</code>!</p>
<p>在处理<code>ref</code>的时候，可以多回顾以下的几个工具函数。这些函数能帮助你更好地处理<code>ref</code></p>
<blockquote>
<p>请注意：ref 将会对传入的数据进行深度响应式，修改其对象或数组中的所有属性都会触发更新。</p>
</blockquote>
<ul>
<li><code>isRef</code>：检查某个值是否为<code>Ref</code>类型的值</li>
<li><code>unref</code>：如果传入的参数是<code>Ref</code>类型，则返回其内部值，否则返回参数本身。如果某些场景下你得到的值可能是<code>Ref</code>，那么可以直接使用<code>unref</code>去取值而不必写判断逻辑。</li>
<li><code>toRef</code>：基于传入的<code>响应式对象</code>的一个属性去创建一个<code>Ref</code>响应式对象，这个对象将会追踪传入的对象的值的变化来同步返回值。</li>
<li><code>toRefs</code>：将传入的响应式对象转换为一个普通对象，这个对象的每个属性都指向源对象对应属性的<code>ref</code>（每个<code>ref</code>都使用<code>toRef</code>创建），如此一来在某些场合下解构将不会丢失响应性。</li>
</ul>
<p>这里为<code>toRefs</code>写一个示例用于解决<code>vue</code>挑战的响应性丢失的问题：</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot;&gt;
import { reactive, toRefs } from &#39;vue&#39;;

function useCount() {
  const state = reactive({
    count: 0,
  });

  function update(value: number) {
    state.count = value;
  }

  return {
    state: toRefs(state),
    update,
  };
}

// Ensure the destructured properties don&#39;t lose their reactivity
const {
  state: { count },
  update,
} = useCount();
&lt;/script&gt;

&lt;template&gt;
  &lt;div&gt;
    &lt;p&gt;
      &lt;span @click=&quot;update(count - 1)&quot;&gt;-&lt;/span&gt;
      {{ count }}
      &lt;span @click=&quot;update(count + 1)&quot;&gt;+&lt;/span&gt;
    &lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>如此一来<code>useCount</code>返回值解构之后，<code>count</code>依然是一个<code>ref</code>响应式对象，源属性改变时<code>count</code>将会更新。</p>
<h3>Shallow Ref</h3>
<p><code>Shallow Ref</code>是<code>ref</code>的浅层作用形式，只有修改<code>.value</code>的时候会触发更新机制，因此在创建大型数据结构的时候我们可以考虑使用浅层优化。</p>
<p>但是如果你真希望对浅层<code>ref</code>的属性进行修改后触发更新，也可以显式地调用<code>triggerRef</code>函数，传入此浅层<code>ref</code>即可。</p>
<h3>Custom Ref</h3>
<p><code>customRef()</code>函数可以创建一个自定义的<code>ref</code>，并且显式声明对其依赖追踪和更新触发的控制方式。先看看其类型签名：</p>
<pre><code class="language-typescript">function customRef&lt;T&gt;(factory: CustomRefFactory&lt;T&gt;): Ref&lt;T&gt;

type CustomRefFactory&lt;T&gt; = (
  track: () =&gt; void,
  trigger: () =&gt; void
) =&gt; {
  get: () =&gt; T
  set: (value: T) =&gt; void
}
</code></pre>
<p>如果你需要创建一个响应式对象，并且对其进行更细粒度的控制（在读写的时候添加逻辑），那么使用<code>customRef</code>是个很好的方式，尤其是在创建<code>composable function</code>的时候。</p>
<p>这里我举两个代码示例：</p>
<p>示例 1：基于<code>sessionStorage</code>的请求缓存<code>Ref</code>：</p>
<pre><code class="language-javascript">import { customRef } from &#39;vue&#39;

function useCache(key, fn) {
  return customRef((track, trigger) =&gt; {
    let cache = sessionStorage.getItem(key)
    if (cache) {
      cache = JSON.parse(cache)
    } else {
      fn().then(data =&gt; {
        cache = data
        sessionStorage.setItem(key, JSON.stringify(cache))
        trigger()
      })
    }
    track()
    return {
      get() {
        track()
        return cache
      },
      set(value) {
        cache = value
        sessionStorage.setItem(key, JSON.stringify(cache))
        trigger()
      }
    }
  })
}

// 使用示例
const dataRef = useCache(&#39;data&#39;, () =&gt; {
  return fetch(&#39;https://api.example.com/data&#39;).then(res =&gt; res.json())
})

// 访问（读取）dataRef 时，如果缓存中有数据，则直接返回缓存的数据，否则进行异步请求，并将结果保存到缓存中
console.log(dataRef.value)
</code></pre>
<p>其中关键的代码是<code>track()</code>和<code>trigger()</code>，二者功能如下：</p>
<ul>
<li>track：调用后立即记录当前函数正在访问数据</li>
<li>trigger：调用后立即更新依赖此数据的组件函数</li>
</ul>
<p>通常，我们应该在<code>get()</code>方法中调用<code>track()</code>函数，在<code>set()</code>方法中调用<code>trigger()</code>函数。</p>
<p>示例 2：封装自定义表单控件</p>
<pre><code class="language-javascript">import { customRef } from &#39;vue&#39;

function useCustomInput(initialValue, validator) {
  return customRef((track, trigger) =&gt; {
    let value = initialValue
    let error = &#39;&#39;
    track()
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        if (validator(newValue)) {
          value = newValue
          error = &#39;&#39;
        } else {
          error = &#39;Invalid input&#39;
        }
        trigger()
      },
      error: () =&gt; error
    }
  })
}

// 使用示例
const nameRef = useCustomInput(&#39;&#39;, value =&gt; value.length &gt;= 3)
console.log(nameRef.value) // &#39;&#39;
console.log(nameRef.error()) // &#39;Invalid input&#39;
nameRef.value = &#39;John&#39;
console.log(nameRef.value) // &#39;John&#39;
console.log(nameRef.error()) // &#39;&#39;
</code></pre>
<p>最后，再来看看<code>vue</code>挑战的自定义防抖<code>ref</code>的题目：</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { watch, customRef } from &#39;vue&#39;;

/**
 * Implement the function
 */
function useDebouncedRef(value, delay = 200) {
  return customRef((track, trigger) =&gt; {
    // 声明 timer 为 null 而不是 0 可以防止可能存在的其他任务 timer 自动分配为 0 导致的任务被不可预测地取消
    let timer = null; // 不能放在 return customRef 上面，否则多个 ref 实例将会访问同一个 timer
    return {
      set(v) {
        // 检查值是否有变化可以减少渲染 trigger
        if(v === value) return
        clearTimeout(timer);
        timer = setTimeout(() =&gt; {
          value = v;
          trigger();
        }, delay);
      },
      get() {
        track();
        return value;
      },
    };
  });
}
const text = useDebouncedRef(&#39;hello&#39;);

/**
 * Make sure the callback only gets triggered once when entered multiple times in a certain timeout
 */
watch(text, (value) =&gt; {
  console.log(value);
});
&lt;/script&gt;

&lt;template&gt;
  &lt;input v-model=&quot;text&quot; /&gt;
&lt;/template&gt;
</code></pre>
<h3>Reactive</h3>
<p>我们可以使用<code>reactive()</code>创建一个响应式代理，这个转换是深层的。举个例子：</p>
<pre><code class="language-vue">const data = reactive({
	meta: {
		text: &#39;x&#39;	
	}
})
</code></pre>
<p>修改<code>data.meta.text</code>的值将会触发更新，如果你使用<code>watch</code>函数侦听<code>data</code>，那么就会执行回调函数。</p>
<p>此外，如果<code>data</code>的某个属性是一个<code>ref</code>：</p>
<pre><code class="language-vue">const count = ref(1)
const obj = reactive({ count })

// ref 会被解包
console.log(obj.count === count.value) // true

// 会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2

// 也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3
</code></pre>
<p>如果我们创建的<code>reactive(obj)</code>响应式代理数据较大，那么深层转换所消耗的性能则越多，并且我们只想要保留对顶层属性的响应性，那么可以使用<code>shallowReactive()</code>来替代以优化性能。</p>
<h3>可写的计算属性</h3>
<p>如果想要控制响应式对象的读写逻辑，可以考虑使用<code>computed</code>接口：</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot;&gt;
import { ref, computed } from &#39;vue&#39;;

const count = ref(1);
const plusOne = computed({
  get() {
    return count.value + 1;
  },
  set(v) {
    count.value = v - 1;
  },
});

/**
 * 确保 `plusOne` 可以被写入。
 * 最终我们得到的结果应该是 `plusOne` 等于 3 和 `count` 等于 2。
*/

plusOne.value++;
&lt;/script&gt;

&lt;template&gt;
  &lt;div&gt;
    &lt;p&gt;{{ count }}&lt;/p&gt;
    &lt;p&gt;{{ plusOne }}&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>通常我们传给<code>computed</code>函数一个函数作为参数，此函数的返回值被转化成一个<code>ref</code>对象，但是如果需要对写进行额外的逻辑控制，则可以不传函数而传一个对象，这个对象如上所示具有<code>set</code>和<code>get</code>属性，其函数签名如下：</p>
<pre><code class="language-typescript">// 只读
function computed&lt;T&gt;(
  getter: () =&gt; T,
  // 查看下方的 &quot;计算属性调试&quot; 链接
  debuggerOptions?: DebuggerOptions
): Readonly&lt;Ref&lt;Readonly&lt;T&gt;&gt;&gt;

// 可写的
function computed&lt;T&gt;(
  options: {
    get: () =&gt; T
    set: (value: T) =&gt; void
  },
  debuggerOptions?: DebuggerOptions
): Ref&lt;T&gt;
</code></pre>
<p>当我们用<code>TypeScript</code>进行开发时，既可以显式地指名<code>T</code>的类型，也可以通过自动类型推断机制让<code>ref</code>自动推断出归属的类型。</p>
<h3>watch everything</h3>
<p>首先，<code>vue</code>支持让开发者监听某个或某些响应式数据源亦或是一个函数的返回值，从而在数据源变化之后调用回调函数。</p>
<p>先来看看<code>watch</code>函数的类型定义：</p>
<pre><code class="language-typescript">// 侦听单个来源
function watch&lt;T&gt;(
  source: WatchSource&lt;T&gt;,
  callback: WatchCallback&lt;T&gt;,
  options?: WatchOptions
): StopHandle

// 侦听多个来源
function watch&lt;T&gt;(
  sources: WatchSource&lt;T&gt;[],
  callback: WatchCallback&lt;T[]&gt;,
  options?: WatchOptions
): StopHandle

type WatchCallback&lt;T&gt; = (
  value: T,
  oldValue: T,
  onCleanup: (cleanupFn: () =&gt; void) =&gt; void
) =&gt; void

type WatchSource&lt;T&gt; =
  | Ref&lt;T&gt; // ref
  | (() =&gt; T) // getter
  | T extends object
  ? T
  : never // 响应式对象

interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // 默认：false
  deep?: boolean // 默认：false
  flush?: &#39;pre&#39; | &#39;post&#39; | &#39;sync&#39; // 默认：&#39;pre&#39;
  onTrack?: (event: DebuggerEvent) =&gt; void
  onTrigger?: (event: DebuggerEvent) =&gt; void
}
</code></pre>
<p>可以看到<code>watch source</code>可以是以下几种类型：</p>
<ul>
<li>一个响应式对象</li>
<li>function</li>
<li>多个观察对象的数组（任意元素改变都将触发回调函数）</li>
</ul>
<p>回调函数的参数基于你的监听类型而定，举个例子如果仅仅监听单个数据源则回调函数将被传入<code>value</code>和<code>oldValue</code>，如果监听的是数组则相应的新值和旧值也是数组。</p>
<p>如果需要处理新旧的值，则可以考虑对回调函数定义添加合理的参数类型。</p>
<p><code>watch</code>除了监听对象和回调函数，还可以添加<code>options</code>来细粒度控制整个代码逻辑。</p>
<p>我们可以为<code>options</code>设置以下的键值：</p>
<ul>
<li>immediate：这是一个布尔值，顾名思义，在创建侦听器的时候立即调用回调函数，其默认值是<code>false</code>。</li>
<li>deep：这也是一个布尔值，如果数据源是一个具有深层键值对的对象或数组，那么整个侦听将会消耗更多性能去检查每一个深层的值的变化，谨慎使用此设置，越深的对象监听的开销越大。</li>
<li>flush： 控制回调函数的执行时机，默认为<code>pre</code>，这意味着在你的回调函数中如果去访问<code>DOM</code>，那么得到的值将会是<code>vue</code>更新之前（组件渲染前）的值，配合<code>pre</code>的字面意思我们也比较容易记下这一点。<ul>
<li>&#39;pre&#39; </li>
<li>&#39;post&#39; 意味着在侦听回调中访问<code>DOM</code>将得到<code>vue</code>更新之后的值</li>
<li>&#39;sync&#39; 仅当回调函数中包含<code>复杂的计算</code>、<code>DOM 更新</code>或<code>发送请求</code>等操作时可以设置此选项，从而让回调函数直接在数据源变化后同步执行。（是的，watch 底层实现通过<code>reactive</code>函数创建一个响应式对象，并且为<code>setter</code>和<code>getter</code>添加了依赖和触发回调函数的逻辑，默认情况下回调函数最终会再次封装成一个异步调度任务<code>SchedulerJob</code>对象）添加到更新队列中，也就是说默认情况下回调并不是<code>同步任务</code>）</li>
</ul>
</li>
</ul>
<p>在回调函数中，我们必须小心不能去做可能导致监听对象变化的操作，这样可能会导致回调循环。</p>
<p>最后，我们或许需要停止侦听器，每个侦听器函数都会返回一个停止侦听的函数，利用这个函数即可停止侦听。</p>
<p>除了<code>watch</code>，我们可能会在其他人代码里看到<code>watchEffect</code>！这算是<code>watch</code>的一个语法糖。</p>
<p>先来看看其类型定义：</p>
<pre><code class="language-typescript">function watchEffect(
  effect: (onCleanup: OnCleanup) =&gt; void,
  options?: WatchEffectOptions
): StopHandle

type OnCleanup = (cleanupFn: () =&gt; void) =&gt; void

interface WatchEffectOptions {
  flush?: &#39;pre&#39; | &#39;post&#39; | &#39;sync&#39; // 默认：&#39;pre&#39;
  onTrack?: (event: DebuggerEvent) =&gt; void
  onTrigger?: (event: DebuggerEvent) =&gt; void
}

type StopHandle = () =&gt; void
</code></pre>
<p>所谓<code>watchEffect</code>，顾名思义“侦听副作用”。其接受一个函数作为参数，并且立即运行这个函数，同时响应式地追踪其依赖，此函数内部的响应式对象即其依赖，依赖更改则会让函数立即执行。</p>
<p>所谓副作用，可以理解为函数或表达式执行时，除了返回一个值以外，还对函数外部的状态产生了影响，比如修改了全局变量，发出了网络请求，更新了<code>DOM</code>等。</p>
<p>相对了<code>watch</code>来说，<code>watchEffect</code>更加灵活，其自动追踪依赖的数据既是优点也是缺点，笔者认为<code>watch</code>通过显式地指名观察的对象能让代码逻辑更加完整，并且可读性更高，也有利于团队开发协作，自动追踪依赖则需要团队成员和开发者更细致地了解整个侦听的逻辑和数据，这有时会让人感到疲惫。</p>
<p>每个人的观点都不一样，不必强求~</p>
<blockquote>
<p><code>watchEffect</code>的<code>options</code>支持<code>onTrack</code>和<code>onTrigger</code>，这两个特性主要用于调试和优化响应式数据，仅当你真的需要做这件事时才方便用到它们。</p>
</blockquote>
<h3>依赖注入</h3>
<p>为了降低多层组件之间利用<code>props</code>传递数据的复杂度，官方提供了<code>依赖注入</code>机制。看看官方文档的这张图：</p>
<p><img src="https://cn.vuejs.org/assets/provide-inject.3e0505e4.png" alt="Provide/inject 模式"></p>
<p>只需要在上层组件利用<code>provide</code>函数提供数据，即可在后代组件里使用<code>inject</code>函数来获取数据。</p>
<p>利用这个<code>API</code>，我们可以在两个方向上提供<code>provide</code>:</p>
<ol>
<li>在应用层 Provide</li>
<li>在组件层  Provide</li>
</ol>
<p>首先看看在应用层<code>provide</code>:</p>
<pre><code class="language-tsx">import { createApp } from &#39;vue&#39;

const app = createApp({})

app.provide(/* 注入名 */ &#39;message&#39;, /* 值 */ &#39;hello!&#39;)
</code></pre>
<p>通常在写插件的时候比较常用，因为定义插件的时候使用<code>app</code>来提供数据比较方便。</p>
<p>其次，看看最为常用的在组件处<code>provide</code>:</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { provide } from &#39;vue&#39;

provide(/* 注入名 */ &#39;message&#39;, /* 值 */ &#39;hello!&#39;)
&lt;/script&gt;
</code></pre>
<p>注入的名字可以是字符串或<code>symbol</code>，推荐使用一个全局文件来保存所有<code>symbol</code>，并且在<code>provide</code>和<code>inject</code>的时候导入使用。</p>
<p>注入的值可以是响应式的对象，注入内部解构也不会自动解包为内部的值，使用响应式数据的时候，如果需要在子组件中修改数据值，官方推荐提供一个修改值的方法来修改数据。</p>
<p>在<code>inject</code>的时候值可能是<code>undefined</code>，这种情况下如果使用<code>TypeScript</code>并且可以确保使用的时候一定存在数据则可以使用<code>as</code>声明值的类型，亦或是<code>inject</code>提供第二个参数作为默认值，默认值可以是一个值也可以是一个函数的返回值，甚至是一个函数。如果注入一个函数，则需要添加第三个参数来说明这是一个函数。</p>
<p>由于笔者日常使用<code>TypeScript</code>进行开发，因此在这里记录一下使用<code>TypeScript</code>标注类型的关键点。</p>
<p>首先，推荐使用一个文件来统一管理<code>provide</code>的<code>key</code>：</p>
<pre><code class="language-ts">const myKey: InjectKey&lt;string&gt; = Symbol();
provide(myKey, &#39;Yo&#39;)
const data = inject(myKey) // TypeScript 能推导出 data 的数据类型为 string
</code></pre>
<p>使用<code>InjectKey&lt;T&gt;</code>来声明此符号对应注入的数据值，后续<code>inject</code>的时候即可自动推导出类型。</p>
<p>最后，我们再来看一个示例：使用依赖注入机制创建一个<code>TypeScript</code>完备的简单状态管理<code>store</code>:</p>
<pre><code class="language-typescript">import { provide, inject, reactive } from &#39;vue&#39;

interface State {
  count: number
}

interface Actions {
  increment: () =&gt; void
  decrement: () =&gt; void
}

const createState = (): State =&gt; {
  return reactive({
    count: 0
  })
}

const createActions = (state: State): Actions =&gt; {
  const increment = () =&gt; {
    state.count++
  }

  const decrement = () =&gt; {
    state.count--
  }

  return {
    increment,
    decrement
  }
}

const stateSymbol = Symbol()
const actionsSymbol = Symbol()

export const provideStore = () =&gt; {
  const state = createState()
  const actions = createActions(state)

  provide&lt;State&gt;(stateSymbol, state)
  provide&lt;Actions&gt;(actionsSymbol, actions)
}

export const useStore = () =&gt; {
  const state = inject&lt;State&gt;(stateSymbol)
  const actions = inject&lt;Actions&gt;(actionsSymbol)

  if (!state || !actions) {
    throw new Error(&#39;Store is not provided&#39;)
  }

  return {
    state,
    actions
  }
}
</code></pre>
<p>从上面的示例中可以看到我们定义了<code>State</code>和<code>Actions</code>接口分别定义状态和操作，然后定义了<code>createState</code>和<code>createActions</code>函数来创建状态和改变状态的操作，在使用时只需要在上层组件定义处调用<code>provideStore</code>函数即可完成初始化状态和改变状态的操作的数据注入。</p>
<p>我们再定义一个<code>useStore</code>函数来辅助获取注入的数据。</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;div&gt;
    &lt;p&gt;Count: {{ state.count }}&lt;/p&gt;
    &lt;button @click=&quot;actions.increment&quot;&gt;+&lt;/button&gt;
    &lt;button @click=&quot;actions.decrement&quot;&gt;-&lt;/button&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script lang=&quot;ts&quot; setup&gt;
import { defineComponent } from &#39;vue&#39;
import { useStore } from &#39;@/store&#39;
const { state, actions } = useStore()
&lt;/script&gt;
</code></pre>
<p>从这个示例衍生出去，我们可以创建良好且<code>TypeScript</code>完备的依赖注入数据管理模式。</p>
<h3>effectScope</h3>
<p><code>effectScope()</code>函数可以创建一个<code>effect作用域</code>，并且捕获其中所创建的响应式副作用（计算属性和侦听器），并且返回一个可以将副作用函数标记为<code>stopped=true</code>的<code>stop</code>函数。</p>
<p>简而言之，我们可以使用如下代码：</p>
<pre><code class="language-typescript">const scope = effectScope()

scope.run(() =&gt; {
  const doubled = computed(() =&gt; counter.value * 2)

  watch(doubled, () =&gt; console.log(doubled.value))

  watchEffect(() =&gt; console.log(&#39;Count: &#39;, doubled.value))
})

// 处理掉当前作用域内的所有 effect
scope.stop()
</code></pre>
<p>以上代码依然可以让计算属性和侦听器按预期运行，但是我们从此得到了一个可以控制副作用集体终止的控制器：<code>scope.stop()</code>函数！</p>
<p>调用此函数之后，此作用域内部的副作用函数都将被标记为<code>stopped: true</code>并且被跳过。</p>
<p>切记：<code>scope.stop()</code>仅仅是标记内部的副作用函数不必再执行，已经执行的副作用函数还是无法停止的。</p>
<h3>修饰符</h3>
<p>修饰符是为了方便开发者处理一些通用逻辑而提供的一种接口，我们可以在指令后面使用修饰符特殊后缀，即可增强或改变指令的功能。</p>
<p><code>vue3</code>修饰符有以下几种：</p>
<ul>
<li>事件修饰符</li>
<li>按键修饰符</li>
<li>鼠标按键修饰符</li>
<li>系统按键修饰符</li>
<li>表单输入绑定修饰符</li>
<li>自定义修饰符</li>
</ul>
<p>了解每一种修饰符都会对我们开发过程中遇到的指令处理有帮助！</p>
<p><strong>当我们开发的时候遇到上述相关功能的时候，推荐先思考自己是否能利用这些修饰符来优化或减少重复的逻辑代码。</strong></p>
<p>首先，我们从事件修饰符开始，事件修饰符有：</p>
<pre><code class="language-jsx">&lt;!-- 单击事件将停止传递 --&gt;
&lt;a @click.stop=&quot;doThis&quot;&gt;&lt;/a&gt;

&lt;!-- 提交事件将不再重新加载页面 --&gt;
&lt;form @submit.prevent=&quot;onSubmit&quot;&gt;&lt;/form&gt;

&lt;!-- 修饰语可以使用链式书写 --&gt;
&lt;a @click.stop.prevent=&quot;doThat&quot;&gt;&lt;/a&gt;

&lt;!-- 也可以只有修饰符 --&gt;
&lt;form @submit.prevent&gt;&lt;/form&gt;

&lt;!-- 仅当 event.target 是元素本身时才会触发事件处理器 --&gt;
&lt;!-- 例如：事件处理器不来自子元素 --&gt;
&lt;div @click.self=&quot;doThat&quot;&gt;...&lt;/div&gt;

&lt;!-- 添加事件监听器时，使用 `capture` 捕获模式 --&gt;
&lt;!-- 例如：指向内部元素的事件，在被内部元素处理前，先被外部处理 --&gt;
&lt;div @click.capture=&quot;doThis&quot;&gt;...&lt;/div&gt;

&lt;!-- 点击事件最多被触发一次 --&gt;
&lt;a @click.once=&quot;doThis&quot;&gt;&lt;/a&gt;

&lt;!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 --&gt;
&lt;!-- 以防其中包含 `event.preventDefault()` --&gt;
&lt;div @scroll.passive=&quot;onScroll&quot;&gt;...&lt;/div&gt;
</code></pre>
<blockquote>
<p>链式书写事件修饰符需要注意顺序，因为相关代码编译后的事件是顺序生成的，例如<code>@click.prevent.self</code>和<code>@click.self.prevent</code>的点击事件阻止范围不同。</p>
</blockquote>
<p>请勿同时使用 <code>.passive</code> 和 <code>.prevent</code>，因为 <code>.passive</code> 已经向浏览器表明了你<em>不想</em>阻止事件的默认行为。如果你这么做了，则 <code>.prevent</code> 会被忽略，并且浏览器会抛出警告。</p>
<p>其次，按键修饰符示例：</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;div&gt;
    &lt;!-- 仅在 `key` 为 `Enter` 时调用 `submit` --&gt;
    &lt;input @keyup.enter=&quot;submit&quot; /&gt;

    &lt;!-- 使用中划线的形式，支持所有按键事件暴露的按键民，不用自己处理案件判断，非常方便 --&gt;
    &lt;input @keyup.page-down=&quot;onPageDown&quot; /&gt;
    &lt;input @keydown.tab=&quot;nextInput&quot; /&gt;
    &lt;input @keydown.delete=&quot;deleteCharacter&quot; /&gt;
    &lt;input @keydown.backspace=&quot;deleteCharacter&quot; /&gt;
    &lt;input @keydown.esc=&quot;cancelForm&quot; /&gt;
    &lt;input @keydown.space=&quot;toggleCheckbox&quot; /&gt;
    &lt;input @keydown.up=&quot;moveUp&quot; /&gt;
    &lt;input @keydown.down=&quot;moveDown&quot; /&gt;
    &lt;input @keydown.left=&quot;moveLeft&quot; /&gt;
    &lt;input @keydown.right=&quot;moveRight&quot; /&gt;
    &lt;!-- 支持组合键 --&gt;
    &lt;input @keydown.ctrl.enter=&quot;submitForm&quot; /&gt;
    &lt;input @keydown.alt.tab=&quot;nextInput&quot; /&gt;
    &lt;input @keydown.shift.delete=&quot;deleteCharacter&quot; /&gt;
    &lt;input @keydown.meta.esc=&quot;cancelForm&quot; /&gt;
    
    &lt;!-- 按键限制： 当按下 Ctrl 时，即使同时按下 Alt 或 Shift 也会触发 --&gt;
    &lt;button @click.ctrl=&quot;onClick&quot;&gt;A&lt;/button&gt;

    &lt;!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 --&gt;
    &lt;button @click.ctrl.exact=&quot;onCtrlClick&quot;&gt;A&lt;/button&gt;

    &lt;!-- 仅当没有按下任何系统按键时触发 --&gt;
    &lt;button @click.exact=&quot;onClick&quot;&gt;A&lt;/button&gt;
    
    &lt;!-- 鼠标点击事件 --&gt;
    &lt;button @click.left=&quot;handleLeftClick&quot;&gt;左键点击&lt;/button&gt;
    &lt;button @click.right=&quot;handleRightClick&quot;&gt;右键点击&lt;/button&gt;
    &lt;button @click.middle=&quot;handleMiddleClick&quot;&gt;中键点击&lt;/button&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>接着，看看表单输入绑定修饰符：</p>
<pre><code class="language-vue">&lt;!-- 在 &quot;change&quot; 事件后同步更新而不是 &quot;input&quot; --&gt;
&lt;input v-model.lazy=&quot;msg&quot; /&gt;
&lt;!-- 自动用parseFloat转为数字，失败则返回原始值 --&gt;
&lt;input v-model.number=&quot;age&quot; /&gt;
&lt;!-- 去除值左右两边空格 --&gt;
&lt;input v-model.trim=&quot;msg&quot; /&gt;
</code></pre>
<h3>directives - 指令</h3>
<p>指令本质上是一个对象，注册到<code>app</code>实例后在实例上全局可用，注册到组件内时，则组件实例可用。</p>
<p>官方文档如是说：</p>
<pre><code class="language-javascript">const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}
</code></pre>
<p>自定义的指令是一个对象，其内部规定以上 <code>key</code>表示的是指令调用的时间段，每个都是可选的周期函数。</p>
<p>来举例说学习一些用法，首先一个例子：自动将<code>v-model</code>绑定的输入字符串首字母转为大写。</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;div&gt;
    &lt;input v-model.capitalize=&quot;message&quot; /&gt;
    &lt;p&gt;Message: {{ message }}&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script lang=&quot;ts&quot;&gt;
import { defineComponent } from &#39;vue&#39;;

// 定义capitalize修饰符
const capitalize = (value: string): string =&gt; {
  if (!value) return &#39;&#39;;
  return value.charAt(0).toUpperCase() + value.slice(1);
};

export default defineComponent({
  name: &#39;MyComponent&#39;,
  data() {
    return {
      message: &#39;&#39;,
    };
  },
  directives: {
    capitalize: {
      beforeUpdate(el, binding) {
        // 将输入值转为大写字母
        const capitalizedValue = capitalize(binding.value);
        // 更新输入框的值
        el.value = capitalizedValue;
        // 更新绑定的值
        binding.value = capitalizedValue;
      },
    },
  },
});
&lt;/script&gt;
</code></pre>
<p>如上所述，利用<code>beforeUpdate</code>函数即可在更新之前操作<code>DOM</code>的值和绑定的值。</p>
<p>再来一个示例：在元素上添加点击外部区域的事件!</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;div&gt;
    &lt;button v-click-outside=&quot;handleClickOutside&quot;&gt;Click Me&lt;/button&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
// 自定义指令：点击元素外部区域触发事件
const clickOutside = {
  // 指令绑定到元素时调用
  mounted(el, binding) {
    // 点击元素外部区域触发事件
    const handleClickOutside = (event) =&gt; {
      if (!el.contains(event.target)) {
        binding.value();
      }
    };
    document.addEventListener(&#39;click&#39;, handleClickOutside);
    // 在元素销毁时移除事件监听
    el._clickOutsideHandler = handleClickOutside;
  },
  // 指令从元素上解绑时调用
  unmounted(el) {
    document.removeEventListener(&#39;click&#39;, el._clickOutsideHandler);
    delete el._clickOutsideHandler;
  },
};

export default {
  name: &#39;MyComponent&#39;,
  directives: {
    clickOutside,
  },
  methods: {
    handleClickOutside() {
      console.log(&#39;Clicked Outside&#39;);
    },
  },
};
&lt;/script&gt;
</code></pre>
<blockquote>
<p><code>el.contains</code> 是一个DOM API中的方法，用于检查一个元素是否包含另一个元素，返回一个布尔值。</p>
</blockquote>
<p>上述监听函数会检查点击的对象是否是绑定对象的子元素，不是则视为点击了外部区域。</p>
<p>再来一个图片懒加载的全局自定义指令例子：</p>
<pre><code class="language-javascript">const lazyloadDirective = {
  created(el) {
    el.classList.add(&#39;img-loading&#39;)
  },
  mounted(el) {
    const img = new Image();
    img.src = el.dataset.src;
    img.onload = function () {
      el.src = el.dataset.src;
      el.classList.remove(&#39;img-loading&#39;);
    };
  },
};

const app = Vue.createApp({
  // ...
});

app.directive(&#39;lazyload&#39;, lazyloadDirective);
</code></pre>
<p>在图片DOM 创建的时候添加一个<code>class</code>:<code>img-loading</code></p>
<pre><code class="language-css">.img-loading {
  position: relative;
}

.img-loading::before {
  content: &quot;&quot;;
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #f5f5f5; /* 占位符背景色 */
  border-radius: 4px; /* 占位符圆角 */
}
</code></pre>
<p>如此一来，在浏览器请求图片数据完成之前都会展示一个简单的骨架样式。</p>
<h3>nextTick()</h3>
<p><code>nextTick(callback)</code>是一个全局方法，其可以解决<code>vue</code>中改变响应式状态时<code>DOM</code>不会立即同步更新的问题，调用这个异步函数即可等待<code>DOM</code>更新，你可以传入回调函数，也可以使用<code>await</code>语法糖来等待<code>DOM</code>更新再添加更多逻辑。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Next.js 新版本 v13.4 来了]]></title>
            <link>https://i5lin.top/blog/post/1699131646</link>
            <guid isPermaLink="false">1699131646</guid>
            <pubDate>Sun, 07 May 2023 16:15:26 GMT</pubDate>
            <description><![CDATA[Next.js v13.4 真的来了

![](https://mobisoftinfotech.com/resources/wp-content/uploads/2022/04/next-JS-framework.png)

2023 年 5 月 5 日，Tim Neutkens 在 NextJS 官网发布了最新的文章来宣告最新版本`Next.js`正式发布。

今天，就让笔者来分享]]></description>
            <content:encoded><![CDATA[<p>Next.js v13.4 真的来了</p>
<p><img src="https://mobisoftinfotech.com/resources/wp-content/uploads/2022/04/next-JS-framework.png" alt=""></p>
<p>2023 年 5 月 5 日，Tim Neutkens 在 NextJS 官网发布了最新的文章来宣告最新版本<code>Next.js</code>正式发布。</p>
<p>今天，就让笔者来分享一下关于<code>Next.js</code>最新版本<code>v13.4</code>的一些消息吧。</p>
<h3>Next.js ？</h3>
<p>为了方便不熟悉<code>Next.js</code>的朋友阅读本文，笔者先简单介绍一下<code>Next.js</code>是什么。</p>
<p><a href="https://nextjs.org/">Next.js by Vercel - The React Framework for the Web</a>：Next.js 是一个基于 React 的轻量级框架，用于构建高性能的 Web 应用程序和静态网站。</p>
<p>它采用了服务器端渲染（SSR）和静态生成（SSG）的技术，可以在客户端和服务器端之间实现快速导航和无缝的数据传输。Next.js 支持自动代码分割、热重载和静态文件服务，并且与许多流行的技术栈和部署平台兼容。</p>
<p>它还提供了一系列丰富的功能，例如动态导入、API 路由、静态优化和预渲染等，可以大大提高开发人员的生产力和开发效率。通过使用 Next.js，开发人员可以轻松构建出具备优秀性能、可维护性和可扩展性的现代 Web 应用程序。</p>
<blockquote>
<p><code>Next.js</code>创立的目标就是提供一个易用的基于<code>React</code>的服务端渲染应用解决方案，乃至创建一个更加动态、个性化和全球化的网络。</p>
</blockquote>
<h3>最新版本</h3>
<p>六个月前，开发团队发布了<code>Next.js v13</code>版本，而最新版本的<code>Next.js</code>已经更新到了<code>v13.4</code>！并且相对于之前的内容有了一些评论两极分化的改动和更新。</p>
<p>首先来看看以下更新内容：</p>
<ul>
<li>APP Router 稳定版<ul>
<li>React 服务器组件</li>
<li>嵌套路由和嵌套布局</li>
<li>简化数据获取</li>
<li>流渲染和Suspense</li>
<li>更好的内置 SEO 功能</li>
</ul>
</li>
<li>Turbopack 测试版</li>
<li>Server Action 测试版</li>
</ul>
<p>在<code>Next.js</code>发布之初（2016 年）便提供了基于目录的约定式路由方案，亦被称为“基于文件系统的路由”，此方案一直是<code>Next.js</code>的核心功能。</p>
<p>随着社区的发展和基于<code>Next.js</code>应用的普及，开发人员发现旧的方案很难实现自定义布局中诸如布局嵌套、单组件的自定义加载和错误状态处理等功能的需求。</p>
<p>在<code>v13.4</code>中，开发者团队发布了一个新的路由器系统：基于新的<code>app/</code>目录创建新的路由系统，开发者不必使用自定义的<code>_app</code>文件来设置全局共享布局，而是转移到了<code>app</code>路由下的<code>layout.ts</code>文件下，并且各个层级的布局可以相互嵌套。</p>
<p>举个例子：</p>
<pre><code class="language-js">// New: App Router ✨
// app/layout.js
//
// The root layout is shared for the entire application
export default function RootLayout({ children }) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body&gt;{children}&lt;/body&gt;
    &lt;/html&gt;
  );
}

// app/dashboard/layout.js
//
// Layouts can be nested and composed
export default function DashboardLayout({ children }) {
  return (
    &lt;section&gt;
      &lt;h1&gt;Dashboard&lt;/h1&gt;
      {children}
    &lt;/section&gt;
  );
}
</code></pre>
<p>此外，除非显式地在文件最上方声明<code>&#39;use client&#39;;</code>来告知编译器这是一个客户端组件，否则所有在<code>app/</code>目录下的组件都是服务端组件。</p>
<p>服务端组件的简单示例：</p>
<pre><code class="language-tsx">// app/page.js

export default async function Page() {
  const res = await fetch(&#39;https://api.example.com/...&#39;);
  // The return value is *not* serialized
  // You can use Date, Map, Set, etc.
  const data = res.json();

  return &#39;...&#39;;
}
</code></pre>
<p>获取数据的函数将会仅在服务端运行，数据获取将会很明显地取决于开发人员的需求，开发者可以获取数据并且组成任何组件（包括项目内组件和第三方支持的服务端组件）。</p>
<blockquote>
<p>更多内容，请阅读官方文档：<a href="https://nextjs.org/docs/getting-started/installation">Getting Started: Installation | Next.js</a>。</p>
</blockquote>
<p>至于<code>Turbopack</code>和<code>Server Action</code>，二者都还处于<code>Beta</code>版本。前者号称最快的打包工具，开发者可以通过<code>next dev --turbo</code>加快本地开发模式下的编译速度，并且很快这个功能就将推进到<code>next build --turbo</code>（官方团队放话）。</p>
<p>后者则是随着<code>React</code>对于表单状态管理和数据缓存、验证等需求的思索产生的<code>服务端</code>解决方案（此前一直是客户端来处理），这将运行开发者在服务器上变更数据，直接调用函数而无需创建基于客户端和服务端的 API 层。</p>
<p>听起来很美好，但现在还不是时候，大多数开发者追求的还是稳定，让子弹先飞一会吧。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，后续笔者将用<code>Next.js</code>实现自己的应用程序，同时也会发一些相关的知识分享，欢迎大家一起讨论👏🏻👏🏻</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Canvas 101]]></title>
            <link>https://i5lin.top/blog/post/1884432121</link>
            <guid isPermaLink="false">1884432121</guid>
            <pubDate>Wed, 06 Sep 2023 17:13:28 GMT</pubDate>
            <description><![CDATA[学习 html canvas 技术]]></description>
            <content:encoded><![CDATA[<h1>Canvas</h1>
<p>通过<code>HTML</code>的<code>canvas</code>元素和<code>JavaScript</code>，我们可以在浏览器环境下绘制各种图形、创建动画、数据可视化，照片处理和实时视频处理等功能。</p>
<h2>Basic Example</h2>
<p>HTML canvas element exmaple:</p>
<pre><code class="language-html">&lt;canvas id=&quot;canvas&quot;&gt;&lt;/canvas&gt;
</code></pre>
<p>JavaScript:</p>
<pre><code class="language-js">const canvas = document.querySelector(&#39;#canvas&#39;) // 获取元素
const ctx = canvas.getContext(&#39;2d&#39;) // 获取 2D 上下文
</code></pre>
<blockquote>
<p>Canvas.getContext(): 返回一个canvas 元素上的绘制上下文对象（如果不支持此功能则返回<code>null</code>)</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext"><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext">HTMLCanvasElement: getContext() method - Web APIs | MDN</a></a></p>
<p>不同的获取<code>context</code>的参数，决定了绘图上下文的能力来源。我们通常会使用浏览器的<code>2d</code>上下文，亦或是选择基于<code>webgl</code>的增强上下文，具体选择什么取决于你的需求。</p>
<p>不同的基础能力也有不同的调优配置，具体可以参考文档。</p>
</blockquote>
<h2>Basic usage of canvas</h2>
<p>默认的<code>canvas</code>元素高<code>300px</code>，宽<code>150px</code>，我们可以通过元素属性和<code>CSS</code>来修改其宽高样式，所有绘制的内容都会在这个元素内部渲染，并且这些内部渲染的图形不会受到<code>Canvas</code>上的<code>CSS</code>影响。</p>
<h3>绘制</h3>
<p>看图如下：</p>
<p><img src="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes/canvas_default_grid.png" alt="Canvas grid with a blue square demonstrating coordinates and axes."></p>
<p><code>Canvas</code>的坐标系，其原点为<code>canvas</code>元素的左上角，接下来我们开始绘制基础图形（复杂的图形都可以拆解为基础图形，我们在开发的时候可以多多思考如何将复杂问题拆解为简单的子问题，逐一解决）。</p>
<p><code>Canvas</code>仅支持两种原始图形：</p>
<ul>
<li>Rectangles 矩形</li>
<li>Paths 路径（多点成线）</li>
</ul>
<p>其他的图形都是通过组合以上的原始图形形成的。</p>
<h4>绘制矩形</h4>
<p>绘制矩形的核心<code>API</code>如下：</p>
<ul>
<li><code>fillRect(x, y, width, height)</code> 填充矩形</li>
<li><code>strokeRect(x, y, width, height)</code> 绘制矩形的外部轮廓</li>
<li><code>clearRect(x, y, width, height)</code> 清除矩形区域数据，将其完全变得透明。</li>
</ul>
<p>看看这个例子：</p>
<pre><code class="language-js">function draw() {
  const canvas = document.getElementById(&quot;canvas&quot;);
  if (canvas.getContext) {
    const ctx = canvas.getContext(&quot;2d&quot;);

    ctx.fillRect(25, 25, 100, 100);
    ctx.clearRect(45, 45, 60, 60);
    ctx.strokeRect(50, 50, 50, 50);
  }
}
draw()
</code></pre>
<p>这个函数将会绘制如下所示的图形：</p>
<p><img src="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes/canvas_rect.png" alt=""></p>
<p>我们可以清楚看到矩形填充区、清理出来的透明矩形区和内部的轮廓边。</p>
<h4>绘制路径</h4>
<blockquote>
<p>路径就是一系列的<code>点</code>的组合，不同的路径连接组合起来就是不同的图形。</p>
</blockquote>
<p>绘制路径的时候，我们会使用到如下绘制函数：</p>
<ul>
<li><code>beginPath()</code> 创建一个路径，创建路径之后就可以在上面绘制了</li>
<li><code>closePath()</code> 关闭路径，添加一条笔直的线到路径的起点上</li>
<li><code>stroke()</code> 绘制图形的边框，绘制线时，仅绘制线的边框，需要手动移动到起始点或者调用<code>closePath()</code>来收拢图形绘制区域</li>
<li><code>fill()</code> 绘制图形并且填充其内部区域，会自动根据点位进行收拢，例如起始点+两次<code>lineTo()</code>即可在<code>fill()</code>的时候绘制其内部区域，形成一个三角形</li>
</ul>
<p>此外，开始一个路径之后即可调用以下函数进行绘制（就像手持画笔那样）：</p>
<ul>
<li><code>moveTo(x, y)</code> 移动画笔到目标坐标<code>(x, y)</code></li>
<li><code>lineTo(x, y)</code> 绘制直线到目标坐标<code>(x, y)</code></li>
</ul>
<p>其中，<code>beginPath</code>函数是创建一条路径的开始。下面是一个简单的绘制三角形的函数示例：</p>
<pre><code class="language-js">function draw() {
  const canvas = document.getElementById(&quot;canvas&quot;);
  if (canvas.getContext) {
    const ctx = canvas.getContext(&quot;2d&quot;);

    ctx.beginPath(); // 开始路径
    ctx.moveTo(75, 50); // 移动画笔到坐标 75, 50
    ctx.lineTo(100, 75); // 绘制直线到坐标 100, 75
    ctx.lineTo(100, 25); // 再次绘制
    ctx.fill(); // 填充画布区（自动围拢成的三角形）
  }
}
</code></pre>
<p>效果如下所示：</p>
<p><img src="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes/triangle.png" alt=""></p>
<p>绘制直线和矩形相对来说还算简单，接下来我们简单看看如何绘制曲线（弧线）和圆形。</p>
<h4>绘制弧线和圆</h4>
<p>绘制弧线和圆的主要<code>API</code>如下所示：</p>
<ul>
<li><code>arc(x, y, radius, startAngle, endAngle, counterckickwise)</code> 默认为顺时针绘制，绘制之后画笔就会停留在最后的坐标上，因此经常需要配合<code>moveTo</code>方法移动画笔</li>
<li><code>arcTo(x1, y1, x2, y2, radius)</code> 绘制弧线以连接两个切线。简单来说我们首先定义起点坐标和终点坐标，接着需要绘制两个点之间的一条弧线。</li>
</ul>
<p>绘制弧线的<code>API</code>是<a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc"><a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc">arc(x, y, radius, startAngle, endAngle, counterclockwise)</a></a>：关键属性即：坐标（决定了圆心位置）、半径、起始角度、是否逆时针绘制。</p>
<p>通过上述几个关键点，我们就可以绘制出想要的弧线。接下来，我们来看这个绘制弧线和圆形组成的微笑画面：</p>
<p><img src="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes/canvas_smiley.png" alt=""></p>
<p>这个图形是由外圆和双眼圆形、笑脸半圆弧线的嘴巴组成的。我们一步步来绘制：</p>
<pre><code class="language-js">function draw() {
  const canvas = document.getElementById(&quot;canvas&quot;);
  if (canvas.getContext) {
    // 针对高设备像素比的屏幕，修正宽高以免画面模糊
    const {width, height} = canvas.getBoundingClientRect();
    const radio = window.devicePixelRatio || 1;
    canvas.width = width * radio;
    canvas.height = height * radio;
    const ctx = canvas.getContext(&quot;2d&quot;);
    // 开始路径
    ctx.beginPath();
    // 绘制外圆，设定圆心坐标 (75, 75)
    // 设定半径、起始角度和逆时针绘制
    ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // Outer circle
    // 绘制结束之后，画笔会停在 (125, 75)
    ctx.moveTo(110, 75); // 将画笔移动到左边 10 像素的位置，继续准备绘制嘴巴
    ctx.arc(75, 75, 35, 0, Math.PI, false); // Mouth (clockwise) // 圆心保持一致，修改半径和起始角度，顺时针绘制好嘴巴
    ctx.moveTo(65, 65); // 移动到左边眼睛去绘制眼睛
    ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // Left eye
    ctx.moveTo(95, 65); // 移动到右边眼睛坐标去绘制
    ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // Right eye
    ctx.stroke();
  }
}
draw()
</code></pre>
<p>在绘制的时候想象成自己正在拿着画笔，逐一确定起始点的位置。</p>
<p>最后我们只需要绘制描边即可。</p>
<p>绘制圆形较为简单，下面通过<a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arcTo"><a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arcTo">CanvasRenderingContext2D: arcTo() method - Web APIs | MDN</a></a>的例子，绘制弧线。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[JavaScript Function]]></title>
            <link>https://i5lin.top/blog/post/1972822593</link>
            <guid isPermaLink="false">1972822593</guid>
            <pubDate>Wed, 01 Nov 2023 17:46:44 GMT</pubDate>
            <description><![CDATA[给 JavaScript 开发者关于 JavaScript 函数的建议。]]></description>
            <content:encoded><![CDATA[<p><img src="https://github.com/Developer27149/blog/assets/23721611/e861d7e8-86d4-408f-9ef7-6862835e7751" alt="image"></p>
<h2>创建简明函数</h2>
<blockquote>
<p>声明：实际开发中的代码可能会更加复杂，后续的示例将尽可能简单。</p>
</blockquote>
<p>编写简明的 JavaScript 函数需要注重的有以下几点</p>
<ul>
<li>简明的函数名</li>
<li>结构良好的函数参数</li>
<li>简明的函数注释（可选）</li>
</ul>
<p>其中，函数名即变量名，因此我可以参考上一篇关于变量的命名建议来命名。其次，函数参数不宜超过 2 个，单一的函数参数易于测试。</p>
<p>超过两个函数参数就可以使用对象进行传参，并且使用<code>ES6</code>的解构语法进行解构。使用对象传参和对象解构能提高可读性，阅读者可以通过函数签名轻松了解函数参数。而在函数内，开发者也可以灵活地使选用传入的参数来处理自己的逻辑。</p>
<p>在函数解构的时候将会从传入的对象克隆其原始数据类型的属性，而对象和数组不会进行克隆，在使用的时候需要明确这一点，以免修改了外部的属性，从而引发难以察觉的<code>Bug</code>。</p>
<p>通过解构还有一个好处，那就是诸如<code>ESLint</code>此类工具可以检测开发者解构了未使用的变量，这对于优化代码可读性和整洁性很有帮助。</p>
<p>最后，推荐大家保持良好的代码注释。我们可以使用<code>JSDoc</code>来简单描述函数参数类型和函数功能，以便于后续阅读调用的函数时能提供良好的签名提示。</p>
<blockquote>
<p>使用 VSCode 可以安装 JSDoc 的插件，便于编写 Doc</p>
</blockquote>
<p>下面是一个例子：</p>
<pre><code class="language-js">// 不好的例子❌
function createMenu(title, body, buttonText, cancelable) {
  // ...
};
// 参数众多，在调用的时候就难以直白地理解其参数的含义
createMenu(&quot;Foo&quot;, &quot;Bar&quot;, &quot;Baz&quot;, true);

// 推荐的写法 ✅

/**
 * 创建菜单
 *
 * @param {Object} options - 选项对象
 * @param {string} options.title - 菜单标题
 * @param {string} options.body - 菜单内容
 * @param {string} options.buttonText - 按钮文本
 * @param {boolean} options.cancellable - 是否可取消
 * @returns {void}
 */
function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

// 即使不看函数签名，光从调用的时候传参的属性名我们就能了解到传如的参数的大致含义
createMenu({
  title: &quot;Foo&quot;,
  body: &quot;Bar&quot;,
  buttonText: &quot;Baz&quot;,
  cancellable: true
});
</code></pre>
<h2>单一原则</h2>
<blockquote>
<p>软件工程中最重要的准则之一就是“函数应该只做一件事”。</p>
</blockquote>
<p>功能越多的函数，其复杂度就越高，并且难以编写、测试和理解。当我们将函数单独隔离开来，让一个函数仅处理一件事，那么这个函数将会非常易于重构。如果你需要修改某个复杂流程中的某个函数的单一功能，那么修改功能单一的函数就能让复杂度降低到最小。</p>
<p>简单地说，单一功能的函数专注于解决一个具体的问题，并且具有清晰的输入和输出。</p>
<p>举个例子：</p>
<pre><code class="language-js">// 不推荐的写法 ❌
function emailClients(clients) {
  clients.forEach(client =&gt; {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

// 推荐的写法 ✅
function emailActiveClients(clients) {
  clients
    .filter(isActiveClient)
    .forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}
</code></pre>
<p>这或许并不是一个很好的例子，但确实能够体现函数仅做一件事的理念。<code>isActiveClient</code>和<code>email</code>函数分别实现自己单一的功能：</p>
<ul>
<li><code>isActiveClient</code>:判断客户端是否可用</li>
<li><code>email</code>:发送邮件</li>
</ul>
<p>而<code>emailClients</code>函数将所有逻辑全部封装在一起，当我们需要修改逻辑的时候，就需要理解更多的上下文。</p>
<h2>函数应该仅有一层抽象</h2>
<p>来看这个例子，我们需要一个<code>parseBetterJSAlternative()</code>函数将代码进行解析和构建语法树进行更多的抽象处理：</p>
<pre><code class="language-js">// 不推荐 ❌
unction parseBetterJSAlternative(code) {
  const REGEXES = [
    // 若干正则表达式...
  ];
	
  // 语句拆分
  const statements = code.split(&quot; &quot;);
  const tokens = [];
  REGEXES.forEach(REGEX =&gt; {
    statements.forEach(statement =&gt; {
      // 遍历处理语句...
    });
  });

  const ast = [];
  tokens.forEach(token =&gt; {
    // 遍历 token，构建抽象语法树...
  });

  ast.forEach(node =&gt; {
    // 解析语法树...
  });
}
</code></pre>
<p>在上述函数中，函数体内需要处理多层抽象逻辑，接下来我们试试把多层抽象独立出去：</p>
<pre><code class="language-js">// 推荐 ✅
// 构建 tokens
function tokenize(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(&quot; &quot;);
  const tokens = [];
  REGEXES.forEach(REGEX =&gt; {
    statements.forEach(statement =&gt; {
      tokens.push(/* ... */);
    });
  });

  return tokens;
}

// 解析 tokens
function parse(tokens) {
  const syntaxTree = [];
  tokens.forEach(token =&gt; {
    syntaxTree.push(/* ... */);
  });

  return syntaxTree;
}

// 单层抽象函数实现
function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const syntaxTree = parse(tokens);
  syntaxTree.forEach(node =&gt; {
    // parse...
  });
}
</code></pre>
<p>相较而言，后者对多层抽象进行拆分，可以提高代码的可读性、降低代码的复杂性。</p>
<h2>去除重复代码</h2>
<blockquote>
<p>我们可以尽最大努力避免重复代码，即使我们现在很忙。</p>
</blockquote>
<p>重复代码是不好的，因为这意味着如果你需要更改某些逻辑，那么就会有多个地方需要更改，这是一件容易遗漏的事情。</p>
<p>重复代码的来源通常是我们需要处理若干件不同的事情，但这些需求里存在通用的逻辑，并且其存在某些特殊的差别让你“不得不”创建多段重复代码来实现这些需求。</p>
<p>为了去除重复代码，我们需要创建一个函数、模块或类来抽象其中的逻辑以应对不同需求的差异。</p>
<blockquote>
<p>尽管糟糕的抽象比重复的代码更加恶心，但还是推荐大家去尝试创建良好的抽象逻辑，不要重复编写代码，否则你或其他人可能会在需要修改的时候花费很大的力气。</p>
</blockquote>
<p>让我们来看看一个重复的代码示例：</p>
<pre><code class="language-js">// 不推荐 ❌
function showDeveloperList(developers) {
  developers.forEach(developer =&gt; {
    const expectedSalary = developer.calculateExpectedSalary();
    const experience = developer.getExperience();
    const githubLink = developer.getGithubLink();
    const data = {
      expectedSalary,
      experience,
      githubLink
    };

    render(data);
  });
}

function showManagerList(managers) {
  managers.forEach(manager =&gt; {
    const expectedSalary = manager.calculateExpectedSalary();
    const experience = manager.getExperience();
    const portfolio = manager.getMBAProjects();
    const data = {
      expectedSalary,
      experience,
      portfolio
    };

    render(data);
  });
}
</code></pre>
<p>接下来，我们将重复的逻辑抽离出来：</p>
<pre><code class="language-js">// 推荐 ✅
function showEmployeeList(employees) {
  employees.forEach(employee =&gt; {
    // 一致的逻辑
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();
		// 通用的数据
    const data = {
      expectedSalary,
      experience
    };
		// 单独的属性
    switch (employee.type) {
      case &quot;manager&quot;:
        data.portfolio = employee.getMBAProjects();
        break;
      case &quot;developer&quot;:
        data.githubLink = employee.getGithubLink();
        break;
    }

    render(data);
  });
}
</code></pre>
<h2>使用 Object.assign 设置默认对象属性</h2>
<p>之前提及多个参数的函数建议优化成单一参数并且使用对象传参，这里我们继续来看一个参数是对象的函数例子：</p>
<pre><code class="language-js">// 不推荐 ❌
const menuConfig = {
  title: null,
  body: &quot;Bar&quot;,
  buttonText: null,
  cancellable: true
};

function createMenu(config) {
  config.title = config.title || &quot;Foo&quot;;
  config.body = config.body || &quot;Bar&quot;;
  config.buttonText = config.buttonText || &quot;Baz&quot;;
  config.cancellable =
    config.cancellable !== undefined ? config.cancellable : true;
}

createMenu(menuConfig);
</code></pre>
<p>上述代码使用<code>||</code>符号来设置初始值，可读性不佳。我们可以使用<code>Object.assign</code>函数来构建一个具有初始化值的新对象：</p>
<pre><code class="language-js">// 推荐 ✅
const menuConfig = {
  title: &quot;Order&quot;,
  // 参数不包含 body 属性
  buttonText: &quot;Send&quot;,
  cancellable: true
};

function createMenu(config) {
  // 克隆的同时，如果不传入某属性，则使用默认值
  const finalConfig = Object.assign(
    {
      title: &quot;Foo&quot;,
      body: &quot;Bar&quot;,
      buttonText: &quot;Baz&quot;,
      cancellable: true
    },
    config
  );

  // finalConfig: {title: &quot;Order&quot;, body: &quot;Bar&quot;, buttonText: &quot;Send&quot;, cancellable: true}
  // ...
}

createMenu(menuConfig);
</code></pre>
<h2>不要使用 Flag 作为函数的参数</h2>
<p>接下来看一个使用<code>Flag(标志)</code>作为参数的函数例子：</p>
<pre><code class="language-js">// 不推荐 ❌
function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}
</code></pre>
<p>使用<code>Flag</code>参数则通常表示函数内部需要处理不同的参数值，这就意味着函数需要处理更多的逻辑问题，相较而言更推荐功能单一的函数：</p>
<pre><code class="language-js">// 简明且单一的函数是更推荐的写法 ✅
function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}
</code></pre>
<p>在工作中我们需要处理的函数逻辑会更加复杂，与此同时功能单一的函数的好处就更加明显。</p>
<h2>消除副作用</h2>
<p>单一功能函数在执行过程中如果对外部的环境产生了可观察的影响，那么我们就可以称之为产生了<code>副作用</code>。</p>
<p>比如：</p>
<ul>
<li>修改外部变量</li>
<li>修改全局变量</li>
<li>发起网络请求</li>
<li>抛出异常</li>
<li>修改<code>DOM</code></li>
<li>写文件</li>
<li>...</li>
</ul>
<p>在软件开发中函数存在副作用是必不可少的，毕竟我们需要执行一些副作用的逻辑来实现需求。因此，我们可以尽可能地使用单个函数去处理单个的副作用。</p>
<p>接下来来看代码例子：</p>
<pre><code class="language-js">// 不推荐 ❌
function addToCart(item) {
  let cart = localStorage.getItem(&#39;cart&#39;); // 从本地存储中获取购物车数据
  if (cart) {
    cart = JSON.parse(cart);
    cart.push(item); // 修改购物车数据
  } else {
    cart = [item];
  }
  localStorage.setItem(&#39;cart&#39;, JSON.stringify(cart)); // 将购物车数据写回本地存储
  console.log(&#39;Item added to cart&#39;);
}

addToCart({ id: 1, name: &#39;Product A&#39;, price: 10 }); // 调用函数
</code></pre>
<p>在上述示例中，<code>addToCart</code> 函数用于将商品添加到购物车。函数首先从本地存储中获取购物车数据，然后将新商品添加到购物车数据中，最后将更新后的购物车数据写回本地存储。函数还在控制台输出添加商品到购物车的信息。</p>
<p>该函数存在多个副作用。首先，它修改了本地存储的购物车数据，即全局状态。其次，它依赖于本地存储中的数据进行操作。此外，函数还使用 <code>console.log</code> 方法在控制台输出信息。</p>
<p>为了减少副作用并提高函数的可测试性和可维护性，可以将本地存储的读写操作封装成独立的函数，并将其作为依赖传递给 <code>addToCart</code> 函数：</p>
<pre><code class="language-js">// 推荐 ✅
function getCartFromLocalStorage() {
  const cart = localStorage.getItem(&#39;cart&#39;);
  return cart ? JSON.parse(cart) : [];
}

function saveCartToLocalStorage(cart) {
  localStorage.setItem(&#39;cart&#39;, JSON.stringify(cart));
}

function addToCart(item, getCart, saveCart) {
  let cart = getCart();
  cart.push(item);
  saveCart(cart);
  console.log(&#39;Item added to cart&#39;);
}

addToCart(
  { id: 1, name: &#39;Product A&#39;, price: 10 },
  getCartFromLocalStorage,
  saveCartToLocalStorage
);
</code></pre>
<p>在这个修改后的示例中，我们将本地存储的读写操作封装成了独立的函数 <code>getCartFromLocalStorage</code> 和 <code>saveCartToLocalStorage</code>。然后，我们修改了 <code>addToCart</code> 函数的签名，使其接受这两个函数作为参数。</p>
<blockquote>
<p>在上述代码示例中，我们还可以继续抽象出通用的<code>localStorage</code>读取和存储的函数，后续可以更好地复用。</p>
</blockquote>
<p>这样，函数内部不再直接操作全局状态，而是通过传递的函数来获取和保存购物车数据。这样做可以减少副作用，使函数更加可控、可测试和可维护。</p>
<h2>不轻易给内置对象的原型添加函数</h2>
<p>在<code>JavaScript</code>中，我们可以给内置对象的原型赋值以增强其灵活性，举个例子：</p>
<pre><code class="language-js">// 不推荐 ❌
Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem =&gt; !hash.has(elem));
};
</code></pre>
<p>上述代码为数组原型添加了一个<code>diff</code>函数，如此一来开发者便可以在不同的地方直接调用这个函数。这是一个非常灵活的机制，但或许过于灵活了。</p>
<p>这样的代码通常在某些新特性的<code>polyfill</code>代码中出现，并不推荐我们在日常的开发中添加这样的函数，同样的运行环境中可能存在不同的人开发的代码功能，编写这样的全局函数可能会带来难以预测的问题。</p>
<h2>封装条件</h2>
<p>看示例：</p>
<pre><code class="language-js">// 不推荐 ❌
// 如果请求正在发送中，并且当前列表为空
if(request.isLoading === true &amp;&amp; list.length === 0) {
  // ...
}
</code></pre>
<p>如上所示是非常常见的逻辑条件判断的代码，更推荐的做法是可以将这类条件封装成起来使用：</p>
<pre><code class="language-js">// 推荐 ✅
const shouldShowSpinner = (request, list) =&gt; {
  return request.isLoading &amp;&amp; isEmpty(list)
}

if(shouldShowSpinner(request, list)) {
  //...
}
</code></pre>
<h2>避免否定条件</h2>
<p>不好的例子❌：</p>
<pre><code class="language-js">function isClientNotPrepare(client) {
  // ...
}
if(!isClientNotPrepare(client)) {
  // ...
}
</code></pre>
<p>推荐的做法✅:</p>
<pre><code class="language-js">function isClientPrepare(client) {
  // ...
}
if(isClientPrepare(client)) {
  // ...
}
</code></pre>
<h2>删除不再使用的代码</h2>
<p>不再使用的代码跟重复的代码一样糟糕，尽管删除这些遗留无用的代码吧，如果你确实需要这些代码，那么你还是可以从代码版本管理工具中找到它们。</p>
<p>不推荐的代码 ❌</p>
<pre><code class="language-js">function oldRequestModule(url) {
  // ...
}

function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker(&quot;apples&quot;, req, &quot;www.inventory-awesome.io&quot;);
</code></pre>
<p>推荐将<code>oldRequestModule</code>函数清理掉。</p>
<h2>减少回调函数</h2>
<p>JavaScript 函数应该避免回调地狱，这是因为回调地狱会导致代码难以理解、调试和维护。</p>
<p>假设我们有一个需求：从服务器获取用户信息，然后根据用户信息获取其订单列表，最后获取每个订单的详细信息。如果使用回调函数来实现，可能会出现回调地狱的情况。</p>
<p>以下是代码示例：</p>
<pre><code class="language-js">getUserInfo(function(userInfo) {
  getOrderList(userInfo.userId, function(orderList) {
    orderList.forEach(function(order) {
      getOrderDetails(order.orderId, function(orderDetails) {
        // 处理订单详细信息
        console.log(orderDetails);
      });
    });
  });
});
</code></pre>
<p>在上面的代码中，每个异步操作都有一个回调函数，而且这些回调函数嵌套在一起，形成了回调地狱。这使得代码难以阅读和理解，而且在处理错误和异常时也变得困难。</p>
<p>为了避免回调地狱，可以使用一些现代的 JavaScript 技术，例如 Promise、async/await 或使用异步库（如 async.js 或 Bluebird）来处理异步操作。</p>
<p>下面是使用 Promise 和 async/await 改写上述代码的示例：</p>
<pre><code class="language-js">getUserInfo()
  .then(function(userInfo) {
    return getOrderList(userInfo.userId);
  })
  .then(function(orderList) {
    return Promise.all(orderList.map(function(order) {
      return getOrderDetails(order.orderId);
    }));
  })
  .then(function(orderDetailsList) {
    orderDetailsList.forEach(function(orderDetails) {
      // 处理订单详细信息
      console.log(orderDetails);
    });
  })
  .catch(function(error) {
    // 处理错误
    console.error(error);
  });
</code></pre>
<p>使用 async/await：</p>
<pre><code class="language-js">async function processOrders() {
  try {
    const userInfo = await getUserInfo();
    const orderList = await getOrderList(userInfo.userId);
    const orderDetailsList = await Promise.all(orderList.map(async function(order) {
      return await getOrderDetails(order.orderId);
    }));

    orderDetailsList.forEach(function(orderDetails) {
      // 处理订单详细信息
      console.log(orderDetails);
    });
  } catch (error) {
    // 处理错误
    console.error(error);
  }
}

processOrders();
</code></pre>
<p>相较之下，使用<code>async/await</code>语法糖是最简洁的，笔者也比较推荐大家保持<code>async/await</code>语法糖的代码风格。</p>
<h2>最后</h2>
<p>好了，关于函数的分享就到此为止了，后续笔者会继续分享<code>class</code>的建议。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[JavaScript variable suggestions for rookie]]></title>
            <link>https://i5lin.top/blog/post/1968808775</link>
            <guid isPermaLink="false">1968808775</guid>
            <pubDate>Mon, 30 Oct 2023 16:47:58 GMT</pubDate>
            <description><![CDATA[给 JavaScript 初级开发者关于变量的一些建议]]></description>
            <content:encoded><![CDATA[<img width="295" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/433603f0-6583-44ab-a195-a89768b36e17"><h2>含义明确且可发音朗读的变量名</h2>
<p>举个例子，我们需要一个变量保存当前的日期</p>
<pre><code class="language-js">// 不够好的命名
// momentjs 提供的格式化函数，将给定特殊的字符串匹配模式传入生成一个与之匹配的字符串
const yyyymmdstr = moment().format(&quot;YYYY/MM/DD&quot;); 

// 推荐的命名
const currentDate = moment().format(&quot;YYYY/MM/DD&quot;);
</code></pre>
<h2>可搜索、可重用的名称</h2>
<p>在我们的编程生涯中，阅读代码有时候甚至比编写代码花费更多的时间。编写可读性高、易于搜索和重用的代码非常重要。</p>
<p>举个例子，我们需要在半小时之后执行某个函数：</p>
<pre><code class="language-js">const fn = () =&gt; {}
// 不推荐的写法
setTimeout(fn, 1800)
// 稍微好一些的代码
setTimeout(fn, 60 * 30)
// 推荐写法
const HALF_HOUR = 60 * 30; // 具有明确意义、可读性高、易于搜索，甚至推荐使用模块来保存项目中的这些常量
setTimeout(fn, HALF_HOUR);
</code></pre>
<h2>避免隐式代码</h2>
<blockquote>
<p>显式大于隐式 </p>
</blockquote>
<p>常见的<code>forEach</code>函数用法：</p>
<pre><code class="language-js">const locations = [&#39;SuZhou&#39;, &#39;WuXi&#39;]
// 便捷的写法
locations.forEach(i =&gt; {
  doSomething();
  ...
  ...
  doOther();
  ...
  useLocation(i); // 当上下文间隔稍长时，`i` 将变得不够清晰
});

// 推荐的写法
locations.forEach(location =&gt; {
  ...
  ...
  useLocation(location); // 坚持显式的代码
});
</code></pre>
<h2>不添加不必要的内容</h2>
<p>如果你的类名或对象名能告诉你一些信息，那么不要在类或对象内的变量名里重复描述：</p>
<pre><code class="language-js">// 不推荐的写法
const Car = {
  carMake: &quot;Honda&quot;,
  carColor: &quot;Red&quot;,
};

// 推荐的写法
const Car = {
  make: &quot;Honda&quot;, 
  color: &quot;White&quot;
};
</code></pre>
<h2>默认参数</h2>
<p>默认参数通常比变量通过条件、短路的机制来设置默认值更加简明。并且，在多数编辑器内也能提供更好的提示说明。</p>
<pre><code class="language-js">// 不推荐的写法
function createMicrobrewery(name) {
  const breweryName = name || &quot;Hipster Brew Co.&quot;;
  // ...
};

// 推荐的写法，需要注意是仅当传入的参数是 undefine 时，默认参数才会生效
// 其他 falsy 值（&#39;&#39; / NaN / false / 0 / null）依然会作为参数的值，此时默认参数无效
function createMicrobrewery(name = &quot;Hipster Brew Co.&quot;) {
  // ...
}
</code></pre>
<p>下一章《Function》</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[大闸蟹清蒸很好吃]]></title>
            <link>https://i5lin.top/blog/post/1967119416</link>
            <guid isPermaLink="false">1967119416</guid>
            <pubDate>Sun, 29 Oct 2023 19:12:17 GMT</pubDate>
            <description><![CDATA[第一次吃大闸蟹，这滋味简直回味无穷啊！]]></description>
            <content:encoded><![CDATA[<p>hello 大家好~今天继续给大家带来我的新周刊内容。</p>
<blockquote>
<p>苏州的大闸蟹真好吃啊！</p>
<p>首先去买两只大闸蟹，首选青背百肚黄毛的大闸蟹。其次准备生姜切丝、大蒜切丝，热水半开后捆绑放入开蒸一分钟，然后开盖将之翻肚皮放置，撒上之前的生姜大蒜丝，给每只大闸蟹肚子淋上一勺黄酒或白酒，中火十分钟后再熄火焖上三分钟。这三分钟期间可以小块生姜切碎配上两勺醋和一勺生抽搅拌做酱料。</p>
</blockquote>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/c27d919e-2266-4dfe-a383-1911caaa2142" alt="image"></p>
<blockquote>
<p>封面图：大耗子咪咪</p>
</blockquote>
<p>OK！继续来分享上一周接触到了一些讯息！</p>
<h2>重生之我是土耳其人</h2>
<p><a href="https://zblogs.top/how-to-subscribe-to-turkey-icloud/">[最新教程] 国内用户订阅土耳其区 iCloud+ 攻略 - Justin写字的地方</a>：这篇博客分享了作者如何创建土耳其苹果账户并且进行充值，订阅了低价的<code>icloud</code>空间共享给国区家庭用户的教程。如果你需要较多的苹果<code>icloud</code>存储空间，可以考虑考虑这个方法。</p>
<p>最新消息：土耳其区的<code>icloud</code>订阅价是国区的一半。</p>
<blockquote>
<p>最最新消息，算了还是闲鱼厉害（啥都有，真的省钱）</p>
</blockquote>
<h2>Logo 图片下载</h2>
<img width="773" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/d49aa32c-ab7e-4ee4-ab90-d3c133f8ce7d"><p><a href="https://wangchujiang.com/logo/">logo</a>：这个站点是<code>github</code>用户<code>@jaywcjlove</code>开发的静态应用，你可以在这里下载到国内和国际大厂的<code>Logo</code>图片（jpg/svg/png）</p>
<h2>useHooks</h2>
<p><img src="https://camo.githubusercontent.com/2b5ff3204ef3998bf00ae07b1571ae81bc5b5ae10c7e0e09e67f3aa285116903/68747470733a2f2f757365686f6f6b732e636f6d2f6d6574612f6f672e6a7067" alt=""></p>
<p><a href="https://usehooks.com/">useHooks – The React Hooks Library</a>: 国外著名前端开发教学产品团队 <code>ui.dev</code> 开源的<code>React hooks</code>合集，包含了前端开发工作中常见的功能钩子，简直是居家旅行必备神器。无论你是想快速运用到项目里，让你的同事眼前一亮，亦或是学习其源代码，增强自己的技术，都是不可错过绝佳机会。</p>
<h2>Omnigroup</h2>
<p><img src="https://www.omnigroup.com/assets/img/icons/omnidisksweeper-mac@2x.png" alt="img"></p>
<p><a href="https://www.omnigroup.com/">The Omni Group</a>的产品：<code>OmniDiskSweeper</code>！一款很好用的<code>MacOS</code>磁盘大文件检索工具，方便我们清理这些文件。</p>
<img width="1124" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/484604ad-03e5-44de-bbd1-590874e6185e"><p>除此之外，其团队还开发了以下一系列方向的产品：</p>
<img width="1228" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/58e57018-a275-4ec0-93a4-32674b6449ef"><p>有兴趣的可以了解一下。</p>
<h2>抖音美好体</h2>
<p><img src="https://cdn.zxki.cn/tc/2023/10/26/cf2ce8b93eb64.png" alt=""></p>
<blockquote>
<p> 抖音公开了自己产品内广泛使用的<code>抖音美好体</code>字体，免费商用。</p>
</blockquote>
<p>抖音美好体包含 6763 个中文字符，682 个西文及数字符号，执行国家 GB2312-80 简体中文编码字符标准。作为品牌标准字体，抖音美好体将会率先用于抖音及其子品牌中，助力品牌视觉形象的统一，提升品牌的官方性、权威感。</p>
<h2>zhlint</h2>
<p><a href="https://github.com/zhlint-project/zhlint">zhlint-project/zhlint: A linting tool for Chinese language.</a>：中文和英文内容混合的格式检查工具，可以使用<code>npm</code>全局安装使用。</p>
<h2>Youtube AD Block Script</h2>
<p>最近油管更新了对广告拦截插件的应对策略，旧的插件会被检测到。笔者在<a href="https://www.reddit.com/r/youtube/comments/17hdbpo/solution_for_youtube_ads/">(5) Solution for YouTube ads : youtube</a>这里搜索到了新的解决方案。</p>
<blockquote>
<p>chrome-extension://dhdgffkkebhmkfjojejmpbldmpobfkfo/ask.html?aid=41aad3e7-0ffb-48fa-b2c4-9a41ad9ff238</p>
</blockquote>
<p>在油猴搜索&quot;Remove Adblock thing&quot;安装即可，对了，记得卸载之前的<code>Youtube Adblock</code>插件。</p>
<h2>整洁的 JavaScript 代码</h2>
<p><img src="https://camo.githubusercontent.com/af72fb10accee2e9a59efec2a0b436242fad5bb2a00ca11494e7c759834625cc/68747470733a2f2f7777772e6f736e6577732e636f6d2f696d616765732f636f6d6963732f7774666d2e6a7067" alt=""></p>
<p><a href="https://github.com/ryanmcdermott/clean-code-javascript%EF%BC%9A%E8%BF%99%E4%B8%AA%E4%BB%93%E5%BA%93%E5%88%86%E4%BA%AB%E4%BA%86%E4%B8%80%E7%B3%BB%E5%88%97%E6%9C%89%E5%8A%A9%E4%BA%8E%E7%BC%96%E5%86%99%E6%95%B4%E6%B4%81%E7%9A%84">https://github.com/ryanmcdermott/clean-code-javascript：这个仓库分享了一系列有助于编写整洁的</a> <code>JavaScript</code> 代码的经验教训。</p>
<h2>OpenAI 书籍</h2>
<p>我用夸克网盘分享了「OpenAI：GPT 最佳实践中文大白话版本_未来力场编译.pdf」，点击链接即可保存。打开「夸克APP」在线查看，支持多种文档格式转换。</p>
<p>链接：<a href="https://pan.quark.cn/s/2db01eae1f56">https://pan.quark.cn/s/2db01eae1f56</a></p>
<h2>ChatGPT API 水龙头</h2>
<p><a href="https://faucet.openkey.cloud/">https://faucet.openkey.cloud/</a> 支持每 24 小时免费领取一个<code>$1</code>的令牌用于开发测试<code>AI</code>产品</p>
<blockquote>
<p>评论区有人说负载过高，笔者没有亲自注册申请过，仅提供消息</p>
</blockquote>
<h2>智能合约入门</h2>
<p><a href="https://github.com/slowmist/SlowMist-Learning-Roadmap-for-Becoming-a-Smart-Contract-Auditor">https://github.com/slowmist/SlowMist-Learning-Roadmap-for-Becoming-a-Smart-Contract-Auditor</a></p>
<h2>最后</h2>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Rust simple tutorial step by step]]></title>
            <link>https://i5lin.top/blog/post/1762191930</link>
            <guid isPermaLink="false">1762191930</guid>
            <pubDate>Sun, 18 Jun 2023 09:05:02 GMT</pubDate>
            <description><![CDATA[再次学习 Rust 编程，我可能还是会半途而废]]></description>
            <content:encoded><![CDATA[<h1>Rust</h1>
<p><img src="https://regmedia.co.uk/2020/04/28/shutterstock_rust_logo.jpg?x=648&y=324&crop=1" alt=""></p>
<blockquote>
<p>why learning rust?</p>
</blockquote>
<p>Rust 具有极高的性能和内存安全保证，非常适合系统级编程。</p>
<h2>Day1</h2>
<p>打开<code>Rust</code>的世界：</p>
<pre><code class="language-rust">fn main() {
  println!(&quot;hello rust.&quot;);
}
</code></pre>
<p>如上所示，<code>fn</code>关键字标识了一个函数<code>main</code>，一个可执行的<code>Rust</code>程序有且只有一个<code>main</code>函数，并且是整个程序的入口。</p>
<p>函数名后的括号内可以定义可选的参数体，上述例子不需要参数因此为空。函数体则由<code>{}</code>括号括起来，<code>Rust</code> 花括号内是一个<code>Scope</code>（作用域）！</p>
<blockquote>
<p>Rust Scope 指的是定义在花括号内的变量和常量只在花括号内有效。离开花括号后就会被释放。</p>
</blockquote>
<p><code>println!</code>是一个内置<code>macro</code>（宏），用于打印信息到屏幕。根据官方文档的说法，<code>Rust</code>风格的代码，缩进使用的是四个空格，而非<code>tab</code>键。</p>
<blockquote>
<p>如果使用的是<code>println</code>，不加<code>!</code>则表示是调用函数，而非宏。宏和函数的差别，后续会细说。</p>
</blockquote>
<p>最后，单行语句使用<code>;</code>结尾，表示这一行表达式的结束。</p>
<p>在上述简单示例代码完成后，可以用<code>rustc main.rs</code>编译出一个<code>main</code>文件，在<code>Linux</code>或者<code>MacOS</code>上的终端，可以直接使用<code>./main</code>执行这个程序，最终输入：</p>
<pre><code class="language-bash">hello rust.
</code></pre>
<p>复杂的程序也是由若干个基础模块组合起来的，现在我们也算是打开了<code>Rust</code>的世界之门。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[没有人会偶然成为宇航员]]></title>
            <link>https://i5lin.top/blog/post/1552107581</link>
            <guid isPermaLink="false">1552107581</guid>
            <pubDate>Sun, 22 Jan 2023 12:58:35 GMT</pubDate>
            <description><![CDATA[没有人会偶然成为宇航员.

![](https://images.unsplash.com/photo-1581574919402-5b7d733224d6?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8Z29hbHxlbnwwfDB8MHx8&auto=format&fit=crop&w=800&q=60)

- 没有人会偶然成为宇航员]]></description>
            <content:encoded><![CDATA[<p>没有人会偶然成为宇航员.</p>
<p><img src="https://images.unsplash.com/photo-1581574919402-5b7d733224d6?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8Z29hbHxlbnwwfDB8MHx8&auto=format&fit=crop&w=800&q=60" alt=""></p>
<ul>
<li>没有人会偶然成为宇航员</li>
<li>“产品”</li>
<li>你关注的是什么？你把精力放在哪里？你听信谁的声音？你阅读谁的文字？</li>
</ul>
<h3>趣事趣图</h3>
<p>荷兰的一个图书馆，你想要为电子设备充电的话，必须骑自行车发电😂</p>
<p><img src="https://cdn.beekka.com/blogimg/asset/202212/bg2022120702.webp" alt=""></p>
<p>根据新西兰的一项研究，由于没有打开窗户，公共汽车内部充满了乘客呼出的二氧化碳。空车的时候，二氧化碳浓度是百万分之516，即 516 ppm；乘满乘客20分钟以后，二氧化碳浓度就上升到了 5737 ppm，上升了十多倍。</p>
<p><strong>所以，你在呼吸谁的呼吸。</strong></p>
<p><img src="https://cdn.beekka.com/blogimg/asset/202212/bg2022120820.webp" alt=""></p>
<p>这就是我宁愿走路也不愿挤公交的原因。</p>
<h3>ChatGPT Chrome Extension</h3>
<p>这是一个<code>ChatGPT</code>谷歌插件，安装之后就可以在插件栏使用<code>ChatGPT</code>，并且在谷歌搜索结果的右侧空白区域，为你当前搜索的关键字提供<code>ChatGPT</code>的回答结果。</p>
<p>笔者体验之后感觉不错，在某些时候能直接得到我搜索想要的答案。</p>
<blockquote>
<p><code>ChatGPT</code>真的很强，但由于某种原因国内用户无法注册，并且因为网络原因国内也无法访问，如果需要注册的话则需要一个国外的手机号接收验证码。</p>
</blockquote>
<h3><a href="https://github.com/BlackGlory/parse-favicon#readme"><a href="https://sms-activate.org/en">Virtual numbers for receiving SMS online - SMS-Activate</a></a></h3>
<p>国外的一个接码平台，你可以花一块钱就能得到一个二十分钟有效期的印度手机号。是的，你可以用来注册<code>ChatGPT</code>。</p>
<p>他们的支付平台非常多，你甚至可以用支付宝支付。</p>
<h3><a href="https://trianglify.io/">Trianglify.io · Low Poly Pattern Generator</a></h3>
<p>你是否见过某个站点的背景是非常漂亮的三角形拼接成的图片，这个应用能让你轻松得到一张绚丽的多个三角形合成的图片。</p>
<h3><a href="https://rome.tools/">Rome — Unified developer tools for JavaScript, TypeScript, and the web</a></h3>
<p><code>Rome</code>不是一日建成的，但如果前端配置被它统治，也不是什么坏事。</p>
<p>这个工具的愿景就是让前端开发不在受到无数的配置所困扰，让开发者专注到开发中来。</p>
<blockquote>
<p><code>Rome</code>格式化代码的速度远超<code>eslint</code>或<code>prettier</code>，前端工具链正在被<code>Rust</code>重构。</p>
</blockquote>
<h3><a href="https://nassimsoftware.github.io/zeldabotwstreetview/">Zelda BotW Street View</a></h3>
<p>塞尔达旷野之息的地图，从这里进入塞尔达世界。</p>
<h3><a href="https://eveningkid.com/denodb-docs/">denoDB · MySQL, SQLite, MariaDB, PostgreSQL and MongoDB ORM for Deno</a></h3>
<p><img src="https://eveningkid.com/denodb-docs/img/logo.png" alt=""></p>
<p>MySQL, SQLite, MariaDB, PostgreSQL and MongoDB ORM for Deno. 祝<code>Deno</code>变得更好.</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[攀比会偷走快乐]]></title>
            <link>https://i5lin.top/blog/post/1552107082</link>
            <guid isPermaLink="false">1552107082</guid>
            <pubDate>Sun, 22 Jan 2023 12:56:37 GMT</pubDate>
            <description><![CDATA[攀比会偷走快乐。

![](https://images.unsplash.com/photo-1519238425857-d6922ed3d613?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8bG9uZWx5fGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=800&q=60)

- 攀比会偷走快乐]]></description>
            <content:encoded><![CDATA[<p>攀比会偷走快乐。</p>
<p><img src="https://images.unsplash.com/photo-1519238425857-d6922ed3d613?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8bG9uZWx5fGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=800&q=60" alt=""></p>
<ul>
<li>攀比会偷走快乐</li>
<li>极简主义：舍弃不必要的一切</li>
<li>“过去的经历使我意识到缺乏评论框不会阻碍那些真正想要交流的人，如果真的有必要，即使在没有评论框的情况下，这些人也能通过各种方式向我发送信息，这本身是非常奇妙的。”</li>
<li>花点时间把今天看做一个机会</li>
<li>大多数人只能成为无能的模仿者，如果你也是模仿者，那就去模仿最优质的目标。</li>
<li>有时，最短，最简单的路径可能是错误的路径，而最远，最困难的路径才是正确的。如今几乎所有事情都有摩擦更小，更人性化的解决方案。 遗憾的是，那些能够被动获得的解决方案，其本质多是在向人们兜售某种产品。</li>
</ul>
<h3><a href="https://sli.dev/">Slidev</a></h3>
<p>为开发者打造的演示文稿工具，如果你需要为团队或其他群体做技术分享，或许你会喜欢这个很有技术范的演示文稿工具。</p>
<blockquote>
<p>Slidev 依然在开发中，API和用法尚未稳定，但值得我们期待。</p>
</blockquote>
<h3><a href="https://github.com/BlackGlory/parse-favicon#readme">BlackGlory/parse-favicon: 🌲 Parse HTML to get icon information</a></h3>
<p><code>Node</code>环境下，获取某个站点的<code>favicon</code>的工具</p>
<h3><a href="https://www.hyperui.dev/">Free Open Source Tailwind CSS Components | HyperUI</a></h3>
<p>基于<code>tailwindcss</code>且开源的原生组件示例，无论是直接使用还是逐一学习其实现，都很有现实意义。</p>
<p>我们可以直接利用这些内容来帮助我们实现自己的需求，也可以学习到其构建结构化良好的组件的方式。</p>
<blockquote>
<p>在我看来，其后者的意义对于个人开发者来说更重大，这个真的很不错。我决定将之加入到自己的待办事项之中。</p>
</blockquote>
<h3><a href="https://www.bookstack.cn/books/webapi-tutorial">《阮一峰 Web API 教程》 - 书栈网 · BookStack</a></h3>
<p>阮一峰所著的<code>web api</code>教程，图文并茂。我想每一个<code>web</code>开发者都需要了解这部分的知识，这会在未来某一天你需要写涉及这部分功能的产品时发挥作用。</p>
<h3><a href="https://github.com/nuxt-modules/icon">nuxt-modules/icon: The <Icon> component, supporting Iconify, Emojis and custom components.</a></h3>
<p>这绝对是在<code>Nuxt</code>框架下最佳的<code>Icon</code>解决方案之一，或许可以参考这个组件去构建基于企业或团队内部的<code>SVG Icon</code>解决方案。</p>
<h3><a href="https://responsively.app/">A Web Developer&#39;s Browser | Responsively App</a></h3>
<p>一款可以同时预览web开发响应式效果和滚动截屏的跨平台应用，关键是开源且免费。</p>
<p>如果你也需要为某些项目编写响应式代码，或许这会对你预览效果产生帮助。</p>
<h3><a href="https://www.colorhexa.com/color-names"><a href="https://tinify.cn/">TinyPNG – 智能压缩您的WebP、JPEG和PNG图片</a></a></h3>
<p>这是一个支持图片压缩的在线应用，并且支持API调用。压缩效果很不错，支持<code>5M</code>以下尺寸的免费压缩。</p>
<h3><a href="https://css-tricks.com/gradient-borders-in-css/">Gradient Borders in CSS | CSS-Tricks - CSS-Tricks</a></h3>
<p>基于<code>CSS</code>的渐变边框样式实现方案介绍。</p>
<p>度过了一个美好的周末，我写周刊的能量条又恢复了。</p>
<p>工作好忙，学习如何处理这些事情的同时，恢复自己的能量也很重要。</p>
<p>去看了牙医，麻药打下去我是真的麻了，要好好保护牙齿。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[知道自己想做什么事情的人是幸运的，他们可能这辈子都能乐在其中.]]></title>
            <link>https://i5lin.top/blog/post/1552106143</link>
            <guid isPermaLink="false">1552106143</guid>
            <pubDate>Sun, 22 Jan 2023 12:53:07 GMT</pubDate>
            <description><![CDATA[知道自己想做什么事情的人是幸运的，他们可能这辈子都能乐在其中.

![](https://images.unsplash.com/photo-1558929996-da64ba858215?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTB8fGRvZ3N8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60)
]]></description>
            <content:encoded><![CDATA[<p>知道自己想做什么事情的人是幸运的，他们可能这辈子都能乐在其中.</p>
<p><img src="https://images.unsplash.com/photo-1558929996-da64ba858215?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTB8fGRvZ3N8ZW58MHx8MHx8&auto=format&fit=crop&w=800&q=60" alt=""></p>
<h3><a href="https://github.com/wclr/yalc">wclr/yalc: Work with yarn/npm packages locally like a boss.</a></h3>
<p>让调试第三库变得简单。</p>
<p>开发第三方库的时候，可以看看这个工具。</p>
<h3><a href="https://github.com/unjs/consola#readme">GitHub - unjs/consola: Elegant Console Logger for Node.js and Browser 🐨</a></h3>
<p>现代化的<code>Javascript</code>打印工具（我就是喜欢花里胡哨），支持浏览器和<code>node</code>端。</p>
<p>好吧，优点如下：</p>
<ul>
<li>易用：接口可以随时替换原生<code>console</code>的方法</li>
<li>支持输出回调</li>
<li>支持命令行接口</li>
<li>支持多标签配置</li>
<li>支持输出中断和重启</li>
<li>支持<code>mock</code>数据### <a href="https://nextui.org/">NextUI - Beautiful, fast and modern React UI Library</a></li>
</ul>
<p>React 服务端渲染框架<code>Next</code>官方团队出品的<code>UI</code>库，个人觉得质量还是挺不错的，无论是学习还是在自己的项目里使用，都是一个不错的选择。</p>
<p>目前依然属于<code>beta</code>版本，但是看其进度和更新的频率还是很高效的。</p>
<p>我喜欢。</p>
<h3><a href="https://gitlab.com/gitlab-org/cli">GitLab.org / cli · GitLab</a></h3>
<p>GitHub 有<code>gh</code>命令行程序，<code>Gitlab</code>也有。</p>
<p>我一直喜欢命令行，并且认可“将一切手动处理的事情转化为程序”的观念，限于技术能力不足，但也愿意尝试坚持一步步走下去。</p>
<h3><a href="https://github.com/abhimanyu003/sttr"><a href="https://eduardoboucas.github.io/include-media/#documentation">include-media: Simple, elegant and maintainable media queries in Sass</a></a></h3>
<p><code>sass</code>语言最佳响应式设计断点语义化辅助库，即使现在<code>windicss</code>这样的库非常好用，在响应式上的支持已然足够好用，但是我依然有独立处理<code>sass</code>代码的场景，<code>include-media</code>是我的最爱。</p>
<h3><a href="https://www.hyperui.dev/">Free Open Source Tailwind CSS Components | HyperUI</a></h3>
<p>基于<code>tailwindcss</code>的开源原生组件，不要拿去就用，而是学习到它的实现方式和思维。</p>
<h3><a href="https://www.colorhexa.com/color-names">List of colors</a></h3>
<p><code>web</code>设计之语义化颜色列表。</p>
<blockquote>
<p>从<code>aliceblue</code>搜到这里，留念：</p>
<p><strong>爱丽丝蓝</strong>（英语：Alice-Blue）是一种<a href="https://zh.wikipedia.org/wiki/%E9%A1%8F%E8%89%B2">颜色</a>，是属于<a href="https://zh.wikipedia.org/wiki/Category:%E8%97%8D%E8%89%B2%E7%B3%BB">蓝色系</a>的一种颜色，其颜色接近浅蓝灰色或钢青色。</p>
</blockquote>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[新年新气象]]></title>
            <link>https://i5lin.top/blog/post/1516214913</link>
            <guid isPermaLink="false">1516214913</guid>
            <pubDate>Mon, 02 Jan 2023 09:59:08 GMT</pubDate>
            <description><![CDATA[这是2023年的第一篇周刊，我决定把周刊从单独的仓库放到博客站下面，并且存储在github上面。总之，希望新年能坚持更多东西。



![](https://images.unsplash.com/photo-1588943011511-ef303c037195?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8]]></description>
            <content:encoded><![CDATA[<p>这是2023年的第一篇周刊，我决定把周刊从单独的仓库放到博客站下面，并且存储在github上面。总之，希望新年能坚持更多东西。</p>
<p><img src="https://images.unsplash.com/photo-1588943011511-ef303c037195?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1587&q=80" alt=""></p>
<h3>创业游戏</h3>
<blockquote>
<p><code>notacoward</code>:&quot;Entrepreneurship is like one of those carnival games where you throw darts or something.</p>
<p>Middle class kids can afford one throw. Most miss. A few hit the target and get a small prize. A very few hit the center bullseye and get a bigger prize. Rags to riches! The American Dream lives on.</p>
<p>Rich kids can afford many throws. If they want to, they can try over and over and over again until they hit something and feel good about themselves. Some keep going until they hit the center bullseye, then they give speeches or write blog posts about &quot;meritocracy&quot; and the salutary effects of hard work.</p>
<p>Poor kids aren&#39;t visiting the carnival. They&#39;re the ones working it.&quot;</p>
</blockquote>
<p>创业就像狂欢节游戏，你可以扔飞镖什么的。</p>
<p>中产阶级的孩子扔得起。大部分都没打中。有几个击中了目标，得到了一个小奖品。只有极少数射中了中心的靶心，获得了更大的奖励。白手起家！美国梦永存。</p>
<p>有钱人家的孩子可以买得起很多东西。如果他们愿意，他们可以一遍又一遍地尝试，直到他们撞到什么东西，并对自己感觉良好。有些人一直坚持下去，直到他们成为中心靶心，然后他们发表演讲或写博客文章，谈论“精英管理”和努力工作的有益影响。</p>
<p>可怜的孩子们不是来参观嘉年华的，他们才是工作人员。</p>
<h3>微信读书体验卡</h3>
<p><a href="https://weread.qnmlgb.tech/"><a href="https://weread.qnmlgb.tech/">瓦斯组队 - 无限卡 - 微信读书组队</a></a>：利用微信读书的组队机制，快速匹配陌生人组队即可获取不限次数的会员体验卡。</p>
<h3>李志音乐</h3>
<p><a href="https://lizhi.turkyden.com/#/"><a href="https://lizhi.turkyden.com/#/">李志 BB 音乐作品播放器</a></a>：民间李志音乐爱好者＆web开发者开源的在线播放器，站点资源都收集自网络，并且向李志先生发了邮件告知。</p>
<p>另外，支持打包成移动APP，如果你有MacBook也可以单独下载客户端，如果你有特斯拉😂，也提供了特斯拉车载系统应用。</p>
<h3>Web 应用打包</h3>
<p><a href="https://github.com/tw93/Pake"><a href="https://github.com/tw93/Pake">tw93/Pake: 🤱🏻 Simply make any web page a desktop application using Rust. 🤱🏻 很简单的用 Rust 打包网页生成很小的桌面 App</a></a>，已有的打包应用诸如：</p>
<ul>
<li>微信读书</li>
<li>Twitter</li>
<li>李志音乐</li>
<li>YouTube</li>
<li>...</li>
</ul>
<p>开发者可以自己定制自己的需求，小白也可以去下载某些APP，如果还没有打包也可以发起请求。</p>
<h3>安全的RM命令</h3>
<p><a href="https://github.com/nivekuil/rip"><a href="https://github.com/nivekuil/rip">nivekuil/rip: A safe and ergonomic alternative to rm</a></a>：使用<code>Rust</code>写的命令行程序，更快更安全的删除文件或目录的程序，并且支持误删除恢复。</p>
<h3>克隆CP命令</h3>
<p><a href="https://github.com/tarka/xcp">tarka/xcp: An extended `cp`</a>：依然是<code>Rust</code>重写的命令行程序，克隆了系统的<code>cp</code>命令，更快且具有以下功能：</p>
<ul>
<li>展示进度条</li>
<li>支持<code>.gitignore</code>限制不需要复制的文件</li>
</ul>
<p>并且由于底层调用系统机制的优化，其复制大文件更快，我们可以利用<code>alias</code>直接替换掉<code>cp</code>命令。</p>
<h3>JS全文搜索库</h3>
<p><a href="https://lucaong.github.io/minisearch/"><a href="https://lucaong.github.io/minisearch/">MiniSearch</a></a>：这是一个小而美的全文搜索库，可以在浏览器和<code>Node</code>环境下运行，如果你将要使用JS技术栈去做一些全文搜索，可以找到这个库。</p>
<h3>输入法应用匹配</h3>
<p><a href="https://inputsource.pro/zh-CN?utm_medium=email&utm_source=Revue%20newsletter&utm_campaign=%E5%8F%AF%E4%B9%90%E5%91%A8%E6%8A%A5"><a href="https://inputsource.pro/zh-CN?utm_medium=email&utm_source=Revue%20newsletter&utm_campaign=%E5%8F%AF%E4%B9%90%E5%91%A8%E6%8A%A5">Input Source Pro - 优化 macOS 输入法使用体验</a></a>：切换不同的应用时，自动切换为预设的输入法。</p>
<h3>世界国旗资源</h3>
<p><a href="https://seekflag.com/"><a href="https://seekflag.com/">Seek Flag | Flags of the World</a></a>：免费下载全世界的国家的国旗，提供<code>png</code>和<code>svg</code>格式。</p>
<h3>CSS 迷你课程</h3>
<p><a href="https://doc.clickup.com/37465296/d/13qb6g-64/css-mini-course/13qb6g-84/css-mini-course"><a href="https://doc.clickup.com/37465296/d/13qb6g-64/css-mini-course/13qb6g-84/css-mini-course">CSS Mini Course</a></a>，前提是你能上推特。</p>
<h3>油管私有接口</h3>
<p><a href="https://www.npmjs.com/package/youtubei.js"><a href="https://www.npmjs.com/package/youtubei.js">youtubei.js - npm</a></a>：如果有一天，你想利用油管的数据去做一些有趣的事情，可能这个库能帮到很多忙。</p>
<h3>新世代企业论坛</h3>
<p><a href="https://www.trickle.so/"><a href="https://www.trickle.so/">Trickle – A better way to align your team</a></a>：新世代的人或许会喜欢，定位是企业内的员工可以工作和交流，分享大家的想法，设计和交互非常现代化。</p>
<h3>制作猫的照片</h3>
<p><a href="https://uchinoko-maker.jp/"><a href="https://uchinoko-maker.jp/">【公式】うちのこメーカー | 猫イラストを無料で簡単作成！</a></a></p>
<h3>参考</h3>
<ul>
<li>阮一峰周刊</li>
<li>viggo讯息分享</li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[寻找自己的石头，去碰对手的鸡蛋]]></title>
            <link>https://i5lin.top/blog/post/1562760958</link>
            <guid isPermaLink="false">1562760958</guid>
            <pubDate>Mon, 30 Jan 2023 16:23:32 GMT</pubDate>
            <description><![CDATA[如果你想少走些弯路，就要找到自己的石头去碰对手的鸡蛋。



![](https://images.unsplash.com/photo-1493406300581-484b937cdc41?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80)
]]></description>
            <content:encoded><![CDATA[<p>如果你想少走些弯路，就要找到自己的石头去碰对手的鸡蛋。</p>
<p><img src="https://images.unsplash.com/photo-1493406300581-484b937cdc41?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<h3>CSS压缩工具</h3>
<blockquote>
<p>cssminifier 提供API，我们也可以借助其接口实现自己的CLI，亦或是集成到项目构建流程中去</p>
</blockquote>
<p><a href="https://www.toptal.com/developers/cssminifier">CSS Minifier &amp; Compressor | Toptal®</a>：一个简洁易用的在线<code>CSS</code>压缩工具。</p>
<h3>Open Graph Protocol</h3>
<p><a href="https://ogp.me/">The Open Graph protocol</a>：开放图谱协议。</p>
<blockquote>
<p>The <a href="https://ogp.me/">Open Graph protocol</a> enables any web page to become a rich object in a social graph</p>
</blockquote>
<p>利用开放图谱协议，我们为支持此协议的社交媒体上分享我们的数据，以一种美观的图形对象来展示！简而言之，如果某个社交应用支持此协议，则在分享一个链接时，链接将被渲染成良好可读性的图文内容。</p>
<p>其最早创建于<code>Facebook</code>, 现在国内大多社交媒体都支持了此协议，如果你想分享自己的应用到<del>微信</del>或微博上，则可以参考此协议的规范去设置自己的应用数据。</p>
<h3>Jotai Video Course</h3>
<p><a href="https://daishi.gumroad.com/l/learn-jotai">Learn Simplified Jotai, an atomic state management library for React</a>：大名鼎鼎的React前端状态管理库<code>Jotai</code>作者录制的关于<code>Jotai</code>的视频教程，30刀（或许公司出？😂）。</p>
<blockquote>
<p>如果你是<code>React</code>开发者，这真是个不容错过的资源。</p>
</blockquote>
<h3>JS-Cookie</h3>
<p><a href="https://github.com/js-cookie/js-cookie/tree/latest#readme">js-cookie/js-cookie at latest</a>： 让前端处理<code>cookie</code>变得简单的一款简单、轻量的JS库。优点还是很多的：</p>
<ul>
<li>全浏览器支持（标准浏览器）</li>
<li>可接收任何字符，如果有特殊字符存储或读取的特殊场景，可以让我们少很多麻烦</li>
<li>无依赖，压缩后不足800字节</li>
<li>支持<code>ES Module</code>和<code>AMD/CommonJS</code></li>
<li>文档齐全，用户数量很多，意味着你可以找到很多相关问题的解决思路甚至是答案</li>
<li>...</li>
</ul>
<h3>英语写作检查</h3>
<p><a href="https://trinka.enago.cn/sentence-checker">免费在线句子检查工具——Trinka AI</a>：英语弱鸡的福音，如果你想要写一些英文文档、邮件、简单的信息等等，可以使用这个人工智能驱动的检查工具，其免费额度足够普通用户日常使用。</p>
<p>此工具将为您提供以下功能：</p>
<ul>
<li>句子检查和更正</li>
<li>语法检查</li>
<li>上下文联想表达更正</li>
</ul>
<h3>CSS 设计示例</h3>
<p><a href="https://github.com/chenglou/chenglou.github.io">chenglou/chenglou.github.io</a>：惊人的CSS设计效果，兼容性极强，样式顺滑，非常有学习价值。</p>
<h3>Github Action 本地运行工具</h3>
<p><a href="https://github.com/nektos/act">nektos/act: Run your GitHub Actions locally 🚀</a>，如果你需要写<code>Github Action</code>，那么或许在本地进行测试能给你节省非常多时间。此仓库具有<code>3.4w</code>的star，还等什么快上车！</p>
<h3>Github Contributions 历史图示</h3>
<p><a href="https://green-wall.vercel.app/">Green Wall · GitHub contribution graph generator</a>：输入你的开发者账户，它会展示给你看你的账户的历史提交图示。</p>
<h3>Flask自建图床</h3>
<p><a href="https://green-wall.vercel.app/">Green Wall · GitHub contribution graph generator</a>：基于Flask的web自检图床，支持存储到本地、又拍云、七牛云、阿里云OSS、腾讯云COS、GitHub、Gitee(码云)等， 支持第三方扩展存储到<a href="https://aws.amazon.com/s3/">AWS S3</a>、<a href="https://sm.ms/">sm.ms</a>、<a href="https://www.superbed.cn/">superbed聚合图床</a>等。</p>
<p>无论是学习<code>Python3</code>的<code>Flask</code>项目，还是了解图床自建的思路，本项目都很有借鉴意义。其文档齐全，我们可以照着快速部署自己的图床。</p>
<h3>unocss小程序预设</h3>
<p><a href="https://github.com/MellowCo/unocss-preset-weapp">MellowCo/unocss-preset-weapp: unocss preset for wechat miniprogram，unocss小程序预设，在 taro uniapp 原生小程序 中使用unocss</a></p>
<h3>Storage 本地化存储</h3>
<p><a href="https://github.com/PlasmoHQ/storage">PlasmoHQ/storage: 💾 Safely store data and share it across your extension</a>：此库提供给使用<code>plasmo</code>开发浏览器扩展程序去操作本地化存储的工具，兼容性很好。</p>
<h3>最后</h3>
<p>本周的web开发资讯就分享到这里，我们下周再见。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[使用 shaku 编写更好的技术文章]]></title>
            <link>https://i5lin.top/blog/post/1753146105</link>
            <guid isPermaLink="false">1753146105</guid>
            <pubDate>Mon, 12 Jun 2023 16:47:58 GMT</pubDate>
            <description><![CDATA[Hello， 大家好。这里是妙才的周刊第 38 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀

![](https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/561686587700_.pic-fotor-202306130372.jpg)

> 封面图：喵喵]]></description>
            <content:encoded><![CDATA[<p>Hello， 大家好。这里是妙才的周刊第 38 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/561686587700_.pic-fotor-202306130372.jpg" alt=""></p>
<blockquote>
<p>封面图：喵喵喵</p>
</blockquote>
<p>以下是本周话题：</p>
<h3>写出更好的技术文章：shaku</h3>
<p><a href="https://shaku-web.vercel.app/">shaku-web.vercel.app</a>：shaku 是一个基于<code>React</code>的 markdown 代码示例的库，使用这个库我们可以写出更易读、更优美的代码示例。</p>
<p>举个例子：</p>
<p>默认的<code>markdown</code>渲染：</p>
<pre><code class="language-markdown">```js annotate
const blog = &quot;https://jser.dev&quot;
//                    ^
//       [JSer.dev is the *homepage* for JSer.]
//       [Check it out! [jser.dev](https://jser.dev)]
// This is a normal comment
```
</code></pre>
<p>使用<code>shaku</code>后的渲染效果：</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/NtOOSn.png" alt="image-20230611133949625"></p>
<p>上述<code>markdown</code>内容如果直接渲染成<code>html</code>，其效果是否远不如使用<code>shaku</code>后的示例？</p>
<p>作者利用特定的注释标识，重新设计了渲染逻辑和样式，让代码解释变得更加清晰、可读性大大提高。如果你也有自己的博客，或许可以试试这个库！</p>
<h3>Nextra</h3>
<p><a href="https://nextra.site/">Nextra – Next.js Static Site Generator</a>：基于<code>Next.js</code>的静态站点生成工具，此框架可以轻松生成多语言的页面、支持零配置的全文检索功能。</p>
<p>如果你需要部署一个静态博客或文档应用，非常推荐体验一下。</p>
<p><img src="https://nextra.site/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcard-1.4f54665c.png&w=3840&q=75" alt="img"></p>
<h3>Mailjet</h3>
<p>国外的一个电子邮件服务商，免费的账户提供每天 200 次，每个月发送 6000 封电子邮件的体验额度，并且提供一个邮件编辑器和一系列模板，让用户可以轻松构建自己的邮件内容。</p>
<p>此外，官方还提供了对开发者友好的<code>API</code>服务，支持一系列的开发语言代码示例。</p>
<blockquote>
<p>此外，官方还支持<code>SMS</code>短信服务，有需要的可以深入看看</p>
</blockquote>
<p>如果你需要开发自己的应用，需要做一些订阅、注册验证等功能，非常推荐使用这个站点的服务。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/VKszTZ.png" alt=""></p>
<h3>Icônes</h3>
<p><a href="https://icones.js.org/">Icônes</a>：具有即时搜索功能的图标浏览应用，这个应用满足了我对<code>web</code>开发中的<code>svg</code>图标需求的方方面面。</p>
<p>免费、优秀的搜索功能、丰富的图标资源、支持自定义颜色、在线体验的图片 CDN 地址、专门支持各种框架的代码快速复制、易于下载和共享。</p>
<p><img src="https://github.com/antfu/icones/raw/main/screenshots/1.png" alt=""></p>
<h3>Monica</h3>
<p><a href="https://monica.im/">Monica - Your ChatGPT AI Assistant Chrome Extension</a>：笔者心中最好的<code>Chrome GPT</code>插件，安装完成之后甚至可以在国内直连使用。</p>
<p>这是我的邀请链接：<a href="https://monica.im/?c=RRYYYAEH">https://monica.im/?c=RRYYYAEH</a> 如果你有一些关于这个应用的问题，联系我。</p>
<blockquote>
<p>什么是<code>chatGPT</code>?</p>
<p>人工智能不是未来，ta 已经来了。</p>
<p>ChatGPT 是一种基于 GPT 技术的聊天机器人，可以模拟人类对话，回答用户的问题，提供各种服务。</p>
<p>ChatGPT 可以回答各种问题，如天气、新闻、体育、娱乐、历史、文化等等。此外，它还可以提供翻译、计算、提醒、定位等服务。ChatGPT 还可以与用户进行智能对话，帮助解决问题。</p>
</blockquote>
<p>国家化做得很好，支持多语言无缝切换。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/c20Yms.png" alt=""></p>
<p>每一个设计都可以体会到开发者非常注重用户体验，在任何网页上选择任何文本，只需翻译、解释、总结或使用自己的提示，轻松处理。<img src="https://assets.monica.im/static/media/translate.ac6759fb680013851770.png" alt=""></p>
<p>除了处理文案，翻译等功能，每天还有三十次的免费提问对话额度。</p>
<blockquote>
<p>开通订阅功能更多一些，适合重度使用的用户</p>
</blockquote>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/ajdRvr.png" alt=""></p>
<p>此外，还有更高的套餐：</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/nhA0Lx.png" alt=""></p>
<p>相对于官方<code>20$</code>的价格来说，还是很有吸引力的。</p>
<p>如果我们选择用官方的服务，还需要免得非常多的麻烦事，例如支付、网络、账户风险等等问题。</p>
<p>无可奈何，世界上最大的两股力量都在防着我们。</p>
<h3>Hotkeys-js</h3>
<p><a href="https://github.com/jaywcjlove/hotkeys-js?search=1">hotkeys-js/ at master · jaywcjlove/hotkeys-js · GitHub</a>：原生 JavaScript 处理热键绑定逻辑在<code>web</code>开发中是非常常见的一个需求，尽管这部分的<code>API</code>并不复杂，每个开发者都可以根据文档来实现自己需要处理的需求。但是，我们还是可以借力使用一下<code>hotkeys-js</code>这把瑞士军刀。</p>
<pre><code class="language-bash">╭┈┈╮          ╭┈┈╮  ╭┈┈╮
┆  ├┈┈..┈┈┈┈┈.┆  └┈╮┆  ├┈┈..┈┈┈┈┈..┈┈.┈┈..┈┈┈┈┈.
┆     ┆┆  □  ┆┆   ┈┤┆    &lt; ┆  -__┘┆  ┆  ┆┆__ ┈┈┤
╰┈┈┴┈┈╯╰┈┈┈┈┈╯╰┈┈┈┈╯╰┈┈┴┈┈╯╰┈┈┈┈┈╯╰┈┈┈  ┆╰┈┈┈┈┈╯
                                  ╰┈┈┈┈┈╯
</code></pre>
<p>良好的封装、简洁的使用文档、无依赖纯原生、极小的体积、丰富的测试用例和兼容性优化。</p>
<p>怎能不爱？</p>
<blockquote>
<p><a href="https://github.com/JohannesKlauss/react-hotkeys-hook">JohannesKlauss/react-hotkeys-hook: React hook for using keyboard shortcuts in components.</a> 如果你是<code>react</code>开发者，甚至可以使用这个库来处理按键逻辑，其提供了一个非常<code>react</code>的钩子。</p>
</blockquote>
<h3>Mantine</h3>
<p><a href="https://mantine.dev/">Mantine</a>：一套完整的<code>React</code>UI 解决方案（功能极其丰富），超过 100 个自定义的组件和 50 个功能钩子，让你的<code>React</code>开发更加得心应手。</p>
<p>官方文档还提供了以下几个框架或工具的配合使用指南：</p>
<ul>
<li>NextJS</li>
<li>Vite</li>
<li>Create React app</li>
<li>Remix</li>
<li>Gatsby</li>
</ul>
<p>开发者可以根据自己的需要，定制安装依赖包。</p>
<h3>shadcn UI</h3>
<p><a href="https://ui.shadcn.com/">shadcn/ui</a>：极简冷淡风的 UI 组件，喜欢这种风格的朋友可以看看</p>
<h3>Radix</h3>
<p><a href="https://www.radix-ui.com/">Primitives – Radix UI</a>：无样式组件是未来。</p>
<p>如果我们从零开始设计一个下拉菜单，需要注意什么？</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/HPDmpO.png" alt=""></p>
<ul>
<li>使用箭头键、Escape 和 Enter 快捷键，甚至可以通过输入提示来导航菜单。</li>
<li>支持可勾选项，项目可用于执行操作，也可作为复选框或单选按钮控件。</li>
<li>配置下拉菜单是否允许或阻止外部交互。</li>
<li>支持子菜单</li>
<li>当用户关闭下拉菜单或聚焦到外部元素时，精细控制焦点行为。</li>
<li>实现正确的语义和行为，使其对使用辅助技术的人可访问。</li>
<li>体验良好的打开和关闭动画，优化展开的位置</li>
</ul>
<p>以上这些需求，恐怕没有多少家企业会让员工花时间去做这些功能，出活快才是大多数企业对员工的要求。</p>
<p>在不使用难以定制却具有完备样式的 UI 框架时，重新开发这些功能将会极大消耗开发者的时间和精力，并且对开发者的专业技术能力有较高的要求。</p>
<p>因此，如果有人能为我们开发好这些组件的<code>JavaScript</code>功能就好了，我们只需要按设计师的需求去实现样式即可！</p>
<p><code>Radix</code>为此而生！</p>
<p>甚至，我们可以基于<code>Radix</code>和<code>tailwindcss</code>构建团队的组件库。不要再重复设计组件的细节了，只设计样式就好！</p>
<h3>必应翻译 API</h3>
<p><a href="https://github.com/plainheart/bing-translate-api#readme">plainheart/bing-translate-api: A simple and free API for Bing Translator for Node.js</a>：作者逆向了必应翻译的 Web JavaScript 代码，然后封装成了一个简单的<code>node</code>库，效果堪比国内的一些翻译<code>API</code>。</p>
<h3>x-crawl</h3>
<p><a href="https://github.com/coder-hxl/x-crawl">x-crawl</a>：基于 <code>puppeteer</code> 的 nodejs 的爬虫框架。</p>
<p>功能特性如下：</p>
<ul>
<li>模式切换：异步或同步</li>
<li>爬取优先级设置，可以优先爬取目标</li>
<li>多功能：页面（支持单页应用爬取）、接口、文件等</li>
<li>自动生成配置页面，通过 UI 检视状态</li>
<li>定时爬取</li>
<li>错误重试</li>
<li>代理设置</li>
<li>零配置动态生成设备指纹绕过目标基于浏览器指纹的防护机制</li>
<li>采集录制功能</li>
<li>良好的 TypeScript 支持，提升开发体验</li>
</ul>
<p>对于熟悉 TypeScript 的前端开发者来说，使用这个框架可以比较轻松地处理一些数据爬取采集的工作。</p>
<h3>Div100vh</h3>
<p><a href="https://github.com/mvasin/react-div-100vh">mvasin/react-div-100vh: A workaround for the &#39;100vh&#39; issue in mobile browsers</a>：在 Safari 移动端存在一个臭名昭著的<code>100vh</code>问题，如图所示，移动端<code>safari</code>的<code>100vh</code>的高度包含了导航区域遮挡住的部分视图：</p>
<p><img src="https://raw.githubusercontent.com/mvasin/react-div-100vh/master/images/regular-div.png" alt=""></p>
<p>为了解决这个问题，可以使用这个针对性的方案：</p>
<pre><code class="language-react">import Div100vh from &#39;react-div-100vh&#39;

const MyFullHeightComponent = () =&gt; (
  &lt;Div100vh&gt;
    &lt;marquee&gt;Look ma, no crop!&lt;/marquee&gt;
  &lt;/Div100vh&gt;
)
</code></pre>
<p>具体的细节隐藏在组件里了，如果你有兴趣则查看源代码。</p>
<h3>Ameliorated Windows system</h3>
<p><a href="https://ameliorated.io/#top">Ameliorated.io</a>：构建一个纯净的 Windows 系统（如果你有兴趣的话，可以深入了解一下）。这个应用将会在不影响系统稳定性的前提下移除默认的 Windows 下的很多系统内置的应用，你将会得到一个“纯净”的 Windows 操作系统。</p>
<h3>PropelAuth</h3>
<p><a href="https://www.propelauth.com/">PropelAuth - Authentication for B2B products</a>：在产品的早期阶段，可以尝试使用第三方服务来实现用户注册和多平台登录的功能。</p>
<p>此服务在月活 1000 以下的早期阶段，能免费提供 10000 万封电子邮件的转发额度、支持用户密码登录、单点登录、魔术贴登录、审计日志、用户信息收集等功能。如果顺利你的产品得到了超过 1000 的月活人数，那么升级服务即可立即获得新的服务。</p>
<h3>Konsta UI</h3>
<p><a href="https://konstaui.com/#get-started">Konsta UI - Mobile UI components built with Tailwind CSS</a>：基于<code>tailwindcss</code>的移动端组件解决方案，支持<code>react</code>、<code>vue</code>、<code>svelte</code>。</p>
<h3>flowcv</h3>
<p><a href="https://app.flowcv.com/">Dashboard – FlowCV</a>：极简的简历生成应用，感谢 @linying 分享了这个工具，我用这个工具写了自己的新简历</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[建议独自在外生活的你养一只猫]]></title>
            <link>https://i5lin.top/blog/post/1890879678</link>
            <guid isPermaLink="false">1890879678</guid>
            <pubDate>Mon, 11 Sep 2023 16:37:55 GMT</pubDate>
            <description><![CDATA[hello 大家好~今天继续给大家带来我的新周刊内容。

如果你长时间独自在外生活，那么就养一只猫吧。


![](https://github.com/Developer27149/blog/assets/23721611/80ea2283-9ca1-4573-8325-f17fa5a26b08)



> 封面图：我的小闪电⚡



OK！继续来分享上一周接触到了一些]]></description>
            <content:encoded><![CDATA[<p>hello 大家好~今天继续给大家带来我的新周刊内容。</p>
<p>如果你长时间独自在外生活，那么就养一只猫吧。</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/80ea2283-9ca1-4573-8325-f17fa5a26b08" alt=""></p>
<blockquote>
<p>封面图：我的小闪电⚡</p>
</blockquote>
<p>OK！继续来分享上一周接触到了一些讯息！</p>
<h3>ImageOptim</h3>
<p><img src="https://imageoptim.com/ImageOptim-app.png" alt="Simple drag&#39;n&#39;drop interface"></p>
<p><a href="https://imageoptim.com/howto.html" title="ImageOptim — better Save for Web">ImageOptim — better Save for Web</a>：支持 <code>MacOS</code>本地压缩图片，压缩率很高。</p>
<h3>Hello 算法</h3>
<p><img src="https://www.hello-algo.com/index.assets/conceptual_rendering.png" alt="img"></p>
<p><a href="https://www.hello-algo.com/" title="Hello 算法">Hello 算法</a>开源了 🎉，这是一本通俗易懂的数据结构和算法入门书，非常适合初学者。此外，还提供各种代码示例语言的<code>PDF</code>文件下载。</p>
<h3>Vssue</h3>
<p><img src="https://vssue.js.org/logo.png" alt="hero"></p>
<p><a href="https://vssue.js.org/" title="Vssue">Vssue</a> 一个基于<code>Vue</code>和<code>Github</code>的评论系统插件，可以很方便地集成到你的博客中去。</p>
<p>效果预览如下，有一种清新的感觉。</p>
<p><img src="https://files.mdnice.com/user/9850/aaf2dde6-0f0f-4e19-a51e-d9f1f642c3ce.png" alt=""></p>
<h3>Yesicon</h3>
<p><a href="https://yesicon.app/" title="Yesicon - 精选全球高品质、开源、免费的矢量图标库">Yesicon - 精选全球高品质、开源、免费的矢量图标库</a>，资源齐全，质量上乘，使用也非常方便。</p>
<p>举个例子：</p>
<p><img src="https://files.mdnice.com/user/9850/0b9017f4-2c67-47ce-b878-cbd0ca33e2c6.png" alt=""></p>
<p>随机选择一个资源，其配合的技术栈非常全面，我相信你可以轻松找到你的技术栈对应的使用方案，易用性拉满。</p>
<h3>Web 音视频技术专题</h3>
<p><a href="https://hughfenghen.github.io/tag/WebAV/" title="WebAV Tag | 风痕 · 術&amp;思">WebAV Tag | 风痕 · 術&amp;思</a>：这一系列包含了作者对于音视频 Web 开发的经验分享，相关从业者可以看看。</p>
<h3>PDF 在线工具</h3>
<img width="1156" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/7851de57-4b6b-425e-99fc-7f8ca27172c5"><p><a href="https://www.pdftool.org/zh" title="PDFTool：隐私保护与 PDF 合并和 PDF 签名等无缝 PDF 修改的完美结合">PDFTool：隐私保护与 PDF 合并和 PDF 签名等无缝 PDF 修改的完美结合</a>，此应用支持<code>PWA</code>模式，因此可以下载并且离线使用。</p>
<h3>Figma 新手系列教学 - 草帽sMao</h3>
<p><img src="https://files.mdnice.com/user/9850/8220c17c-2d12-45c2-8bc5-b5eb014fd6b5.png" alt=""></p>
<p><a href="https://www.craft.me/s/dbj8QJkuZ5ruTB" title="Figma 新手系列教学">Figma 新手系列教学</a>: 作者在<code>bilibili</code>发布了一系列的<code>Figma</code>教程，有兴趣的可以关注一下。</p>
<blockquote>
<p><a href="https://www.craft.do/" title="Craft – The Future of Documents">Craft – The Future of Documents</a>：上述作者的描述内容发布在<code>Craft</code>上面，看起来颜值很高的样子。</p>
</blockquote>
<h3>深入架构原理和落地实践</h3>
<p><a href="https://www.thebyte.com.cn/" title="深入架构原理与实践 | 深入架构原理与落地实践">深入架构原理与实践 | 深入架构原理与落地实践</a>：开源的架构小书，目前作者还在写。已经写好的部分包含以下章节：</p>
<ul>
<li>云原生技术概论</li>
<li>深入Linux内核网络及实践</li>
<li>构建足够快的网络服务</li>
<li>负载均衡原理和网关架构应用实践</li>
<li>分布式系统理论和应用实践</li>
<li>容器技术原理概论和应用实践</li>
<li>容器编排系统 Kubernets</li>
<li>服务治理变革 ServiceMesh</li>
<li>GitOps 的落地与实施</li>
<li>架构可观测性</li>
<li>Serverless 架构</li>
<li>FinOps 云成本管理</li>
</ul>
<h3>Radix For Vue</h3>
<p><a href="https://www.radix-vue.com/" title="Radix Vue | Unstyled, accessible components for building high‑quality design systems and web apps in Vue">Radix Vue | Unstyled, accessible components for building high‑quality design systems and web apps in Vue</a>：有人在<code>X</code>上说<code>Radix</code>真好，我要放弃<code>Vue</code>去写<code>React</code>了。接着，著名网友尤小右就在其下评论推荐了这玩意。</p>
<p><code>Radix</code>是一款无样式设计系统，其提供了众多包含了很多刚需逻辑的常用组件，我们可以轻松将之引入到我们的项目中来，其内置的逻辑能大大减少我们的开发量。</p>
<h3>Quick Reference</h3>
<p><img src="https://user-images.githubusercontent.com/1680273/201931931-d8559417-0a15-46af-a009-ec1e56e5b778.png" alt=""></p>
<p><a href="https://wangchujiang.com/reference/" title="Quick Reference &amp; Quick Reference">Quick Reference &amp; Quick Reference</a> 是一个为软件开发人员提供编程知识快速整理分享的站点，你可以在这里找到特定领域编程知识的内容总览和关键知识示例、方便大家的技术栈查询。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[分享我日常在用的谷歌插件]]></title>
            <link>https://i5lin.top/blog/post/1879166613</link>
            <guid isPermaLink="false">1879166613</guid>
            <pubDate>Sun, 03 Sep 2023 17:01:00 GMT</pubDate>
            <description><![CDATA[hello 大家好~今天继续给大家带来我的新周刊内容。

这一周接触的讯息较少，因此周刊稍微分享一下我自己在用的谷歌插件吧！

![闪电](https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230903235837463.png)





> 封面图：我的小闪电⚡还挺喜欢新的猫抓板的嘛！

]]></description>
            <content:encoded><![CDATA[<p>hello 大家好~今天继续给大家带来我的新周刊内容。</p>
<p>这一周接触的讯息较少，因此周刊稍微分享一下我自己在用的谷歌插件吧！</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230903235837463.png" alt="闪电"></p>
<blockquote>
<p>封面图：我的小闪电⚡还挺喜欢新的猫抓板的嘛！</p>
</blockquote>
<p>OK！继续继续！作为一个<code>Chrome 插件</code>重度使用者和开发者，今天我就来分享分享我自己在用的 chrome 插件！排名不分先后！</p>
<h3>扩展管理器（Extension Manager）</h3>
<p><img src="https://lh3.googleusercontent.com/i1quYLmDyiU7N5ES24UQEj74GRSnnMn7eBRwXLBfUK6zwaqWDzmmRAPH6YI7ih0aVKXdH9giVh5W7OjDs4sGdor_=w640-h400-e365-rj-sc0x00ffffff" alt="img"></p>
<p><a href="https://chrome.google.com/webstore/detail/extension-manager/gjldcdngmdknpinoemndlidpcabkggco?authuser=1" title="扩展管理器（Extension Manager） - Chrome 应用商店">扩展管理器（Extension Manager） - Chrome 应用商店</a>：快速管理我的所有扩展程序，相对于谷歌官方的管理页面来说会更好用一些。</p>
<h3>Thunder下载管理器</h3>
<p><img src="https://lh3.googleusercontent.com/eG1yD5MBuubVjxv3D_U0qSaRYCJ3mOtGMZtWJ9_vasD5H5Vt5wh6nP8PC5J8MgdeVvyRMKniraUVFyaZSMVQdcfZ_w=w640-h400-e365-rj-sc0x00ffffff" alt="img"></p>
<p><a href="https://chrome.google.com/webstore/detail/thunder-download-manager/nllgadppmkoamdlocamdnmimkehhojai?authuser=1" title="Thunder下载管理器 - Chrome 应用商店">Thunder下载管理器 - Chrome 应用商店</a>：在谷歌优化自己的下载页面或提供自己的下载插件之前，这个插件一直是我主力在用的下载管理插件。界面简单而实用。</p>
<h3>篡改猴</h3>
<p><img src="https://lh3.googleusercontent.com/1bMLl6dpPsjuKLxnEp_7PING8wGO4pA9DlAoTreNS7TlqD-wS6zBzDABMfiAgd6TJVbWkeCl4sZWZdyCsyynHkPJeQ=w640-h400-e365-rj-sc0x00ffffff" alt="img"></p>
<p><a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?authuser=1" title="篡改猴 - Chrome 应用商店">篡改猴 - Chrome 应用商店</a>：大名鼎鼎的油猴插件，你可以下载很多特定功能的开发者分享的插件，比如某些站点的视频下载。</p>
<h3>Adblock Plus - 免费的广告拦截器</h3>
<p><img src="https://lh3.googleusercontent.com/Tj35Pv-xR5Z87co1mOM2lCA61IywNAigZyXg6tjGzpj6jjKrrb_JLWI2L-oddSfkxOJsuwqAVG_6ZHXJC8rlSDMC4w=w640-h400-e365-rj-sc0x00ffffff" alt="img"></p>
<p><a href="https://chrome.google.com/webstore/detail/adblock-plus-free-ad-bloc/cfhdojbkjhnklbpkdaibdccddilifddb" title="Adblock Plus">Adblock Plus</a>：广告屏蔽插件，非常实用。</p>
<p>阻止 YouTube™ 广告、弹出窗口并抵御恶意软件！</p>
<p>享受没有恼人广告的网络世界。</p>
<p>Adblock Plus for Google Chrome 会阻挡：</p>
<ul>
<li>横幅</li>
<li>YouTube 视频广告</li>
<li>Facebook 广告</li>
<li>弹出窗口</li>
<li>所有其他显眼的广告</li>
</ul>
<h3>Copy as Markdown</h3>
<p><a href="https://chrome.google.com/webstore/detail/copy-as-markdown/fkeaekngjflipcockcnpobkpbbfbhmdn?authuser=1" title="Copy as Markdown - Chrome 应用商店">Copy as Markdown - Chrome 应用商店</a>：很好用的“将页面、图片、链接等资源复制为<code>markdown</code>内容”的插件。</p>
<p>如果你经常需要复制一些内容为<code>markdown</code>语法链接，那么非常推荐这个插件，确实非常好用。</p>
<h3>Copy As Plain Text</h3>
<p><img src="https://lh3.googleusercontent.com/eChfx438jjYU7al1WilBWM24dIt-PTNeCD3ZCyjsUaZC5M-zOobwJ0OsUOAVzGv9bpUwYFJU7Bl9_hf-150Uf3oY=w640-h400-e365-rj-sc0x00ffffff" alt="img"></p>
<p><a href="https://chrome.google.com/webstore/detail/copy-as-plain-text/eneajgkmdhmjmloiabgkpkiooaejmlpk?authuser=1" title="Copy As Plain Text - Chrome 应用商店">Copy As Plain Text - Chrome 应用商店</a>：有些页面复制文本后会添加一些不需要的格式或数据，这个插件在复制纯文本上还挺好用的。</p>
<h3>Erin</h3>
<p><a href="https://chrome.google.com/webstore/detail/erin/nobdiagkeelcndaijbdninbmjkdahggd?authuser=1" title="Erin - Chrome 应用商店">Erin - Chrome 应用商店</a>：我开发的新标签页插件，如果你对系统默认的新标签页或者某些广告很多的新标签页感到腻味，而恰好也喜欢壁纸和需要添加一些稍后阅读的页面，亦或是固定一些书签在新标签页下，那么或许你会喜欢这个插件。</p>
<p><img src="https://erin-homepage.vercel.app/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmain.b2070029.png&w=3840&q=75" alt=""></p>
<h3>Jiffy Reader</h3>
<p><a href="https://chrome.google.com/webstore/detail/jiffy-reader/lljedihjnnjjefafchaljkhbpfhfkdic?authuser=1" title="Jiffy Reader - Chrome 应用商店">Jiffy Reader - Chrome 应用商店</a>：听说将英文的前缀单词高亮，能提高阅读者的阅读理解速度。这个插件主要是提供这一功能，笔者尝试之后确实感觉在高亮了关键的英语前缀之后，自己对于英语语句的理解速度有了提升。</p>
<p><img src="https://lh3.googleusercontent.com/Mg6QxSuXburXCs1sRaO6gNOHYf1nXwVPIoCH77Qx7Yiuoz8W22VPldTvqyM84-qNRSGezVQp1Z0rksAr0etBCvJB=w640-h400-e365-rj-sc0x00ffffff" alt="img"></p>
<h3>JSON-handle</h3>
<p><a href="https://chrome.google.com/webstore/detail/json-handle/iahnhfdhidomcpggpaimmmahffihkfnj?authuser=1" title="JSON-handle - Chrome 应用商店">JSON-handle - Chrome 应用商店</a>：支持 JSON 数据的预览和快速跳转修改、复制等操作。</p>
<p><img src="https://lh3.googleusercontent.com/Q7b4wi7gG4SJhFxT3n633dFHVcm1XNn7UUA87bblaUoT32aUQrn6VygY_vzH7W6yE2of4ypEzrJetlO9LDHzFzucoA=w640-h400-e365-rj-sc0x00ffffff" alt="img"></p>
<p>作为一个<code>web</code>开发者，很多时候我们需要处理一些<code>JSON</code>数据，这个插件还是挺好的。</p>
<h3>LastPass: Free Password Manager</h3>
<p><img src="https://lh3.googleusercontent.com/IWUSDcCYEkA5luU2MB_ahrb9LG6XSCOcHpGpBEVf2Tse3d3X_cHPb4FasICIPRpC8xSWJF5ACrprIXVP3n1Zr7aP=w640-h400-e365-rj-sc0x00ffffff" alt="img"></p>
<p><a href="https://chrome.google.com/webstore/detail/lastpass-free-password-ma/hdokiejnpimakedhajhdlcegeplioahd?authuser=1" title="LastPass: Free Password Manager - Chrome 应用商店">LastPass: Free Password Manager - Chrome 应用商店</a>：著名的密码管理工具<code>LassPass</code>的浏览器插件版本，可以快速为你创建不同强度的密码。为你保存、自动填写、自动登录等等关于密码交互的功能。</p>
<blockquote>
<p>1Password 的平替！</p>
</blockquote>
<p>简单来说就是：<code>LastPass</code>可以保存您的密码，并在每台计算机和移动设备上提供安全访问。<code>LastPass</code>让您掌控在线生活，轻松保护您的关键信息，确保安全，并在您需要时随时随地访问。将所有密码、地址、信用卡等保存在您的安全保险库中，<code>LastPass</code>在您需要时会自动填写您的信息。</p>
<p>不要再浪费时间被锁在账户外或填写在线密码和表单了 -<code> LastPass</code>简化您的日常在线任务。</p>
<h3>uBlacklist</h3>
<p><img src="https://lh3.googleusercontent.com/eG7PFI8RdHYezHWXrVZkaGmQ0BPkKrRwgDe1VN-dcsuZPd7efH4DZ9ZgVfxQU0qndRu7lGCOo4NyYrO-MDfnIIY2MB8=w640-h400-e365-rj-sc0x00ffffff" alt="img"></p>
<p><a href="https://chrome.google.com/webstore/detail/ublacklist/pncfbmialoiaghdehhbnbhkkgmjanfhe?authuser=1" title="uBlacklist - Chrome 应用商店">uBlacklist - Chrome 应用商店</a>：在谷歌的搜索结果中屏蔽特定的网站（比如<code>CSDN</code>）显示。</p>
<p>此浏览器扩展可以帮你在谷歌搜索结果中屏蔽一些你不想看到的网站。</p>
<p>你可以通过在搜索结果页面中直接屏蔽某个网站，也可以通过点击本扩展的图标将当前网站添加到黑名单。屏蔽规则同时支持「匹配模式」（比如 <em>://</em>.example.com/*）和「正则表达式」（比如 /example.(net|org)/）的语法。</p>
<p>本浏览器扩展还提供了黑名单的同步功能，可以通过 Google Drive 和 Dropbox 将黑名单列表在不同设备之间同步。除此之外，你还可以通过订阅一些公开的黑名单列表，随时获取最新的网站黑名单。</p>
<h3>Vimium</h3>
<p><a href="https://chrome.google.com/webstore/detail/vimium/dbepggeogbaibhgnhhndojpepiihcmeb?authuser=1" title="Vimium - Chrome 应用商店">Vimium - Chrome 应用商店</a>：<code>Vim</code>用户神器，支持像使用<code>Vim</code>一样使用键盘控制浏览器页面。</p>
<blockquote>
<p>这个插件帮助我减少了鼠标的使用，强推！</p>
</blockquote>
<p>你可以轻松快速地跳转以及使用打开页面、查询所有 Tab 页面和书签、快速搜索、滚动切换等功能。</p>
<h3>酷时钟</h3>
<p><a href="https://chrome.google.com/webstore/detail/cool-clock/icegcmhgphfkgglbljbkdegiaaihifce?authuser=1" title="酷时钟 - Chrome 应用商店">酷时钟 - Chrome 应用商店</a>：这个插件非常的旧了，目前我唯一使用的功能就是通过它直接在浏览器地址栏右边查看时间，它会动态渲染一个时间数据成插件的<code>Icon</code>，在全屏下我可以通过这个插件快速了解还有多久下班。嘿嘿。</p>
<h3>问知乎</h3>
<p><a href="https://chrome.google.com/webstore/detail/%E9%97%AE%E7%9F%A5%E4%B9%8E/okcchoellbegnneigaicdalncakaemji?authuser=1" title="问知乎 - Chrome 应用商店">问知乎 - Chrome 应用商店</a>：通过在地址栏输入<code>z</code> 和空格设定搜索的内容转移到知乎的搜索页面上去（笔者曾经是知乎的重度用户，因此才开发这一插件）。</p>
<h3>TabX - 辅助管理浏览器标签页</h3>
<p><img src="https://files.mdnice.com/user/9850/667b5b5b-285c-4d9e-a265-a74cc3a6a29d.png" alt=""></p>
<p><a href="https://chrome.google.com/webstore/detail/tabx-%E8%BE%85%E5%8A%A9%E7%AE%A1%E7%90%86%E6%B5%8F%E8%A7%88%E5%99%A8%E6%A0%87%E7%AD%BE%E9%A1%B5/jomfeifdkncokmfdhinficpdhohfiifj?authuser=1" title="TabX - 辅助管理浏览器标签页 - Chrome 应用商店">TabX - 辅助管理浏览器标签页 - Chrome 应用商店</a>：快速管理和操作浏览器所有的标签页，支持搜索、状态区分、域名分类和简单的数据统计。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[当我们没有获得 60 分的满足时，我们很容易会去追寻 100 分的满足，而追寻 100 分的满足注定只会感受到深深的孤独感。]]></title>
            <link>https://i5lin.top/blog/post/1863556532</link>
            <guid isPermaLink="false">1863556532</guid>
            <pubDate>Wed, 23 Aug 2023 15:24:12 GMT</pubDate>
            <description><![CDATA[这一周特别想分享看圆桌派的时候听到的一句话，大致意思就是：“当我们没有获得 60 分的满足时，我们很容易会去追寻 100 分的满足，而追寻 100 分的满足注定只会感受到深深的孤独感。”


![image](https://github.com/Developer27149/blog/assets/23721611/afce26d7-27fc-4e44-a772-2e7ad94c1c4a)]]></description>
            <content:encoded><![CDATA[<p>这一周特别想分享看圆桌派的时候听到的一句话，大致意思就是：“当我们没有获得 60 分的满足时，我们很容易会去追寻 100 分的满足，而追寻 100 分的满足注定只会感受到深深的孤独感。”</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/afce26d7-27fc-4e44-a772-2e7ad94c1c4a" alt="image"></p>
<blockquote>
<p>封面图：我的小闪电⚡</p>
</blockquote>
<p>OK！继续来分享上一周接触到了一些讯息！</p>
<h3>Atropos</h3>
<p><img src="https://files.mdnice.com/user/9850/d688d4ef-8aae-483d-af8b-a4b8811bbda2.png" alt=""></p>
<p><a href="https://atroposjs.com/" title="Atropos - Stunning touch-friendly 3D parallax hover effects"><a href="https://atroposjs.com/">Atropos - Stunning touch-friendly 3D parallax hover effects</a></a> 绚丽的 3D 触摸 CSS 动画样式效果实现，不妨试试应用到你的产品中去试试看，效果非常棒！</p>
<h3>JavaScript设计模式与开发实践</h3>
<p>网友分享的<code>JavaScript设计模式与开发实践</code> .pdf: <a href="https://url04.ctfile.com/f/45908404-923787015-a48122?p=8628">https://url04.ctfile.com/f/45908404-923787015-a48122?p=8628</a> (访问密码: 8628)</p>
<h3>终端风格的博客</h3>
<p><img src="https://camo.githubusercontent.com/d01f4e514a31505e22e40b143b9f3a02686c3084cfe5672df5446d54849367de/68747470733a2f2f7465726d696e616c2e6465762e6175746f6d61642e6f72672f7368617265642f7465726d696e616c2d6461726b2d312e706e67" alt="img"></p>
<p><a href="https://github.com/marcantondahmen/automad-terminal" title="marcantondahmen/automad-terminal: A minimal Automad blog theme for terminal lovers"><a href="https://github.com/marcantondahmen/automad-terminal">marcantondahmen/automad-terminal: A minimal Automad blog theme for terminal lovers</a></a> 这个仓库存放了这个终端风格的站点的源代码，100% PHP 实现！</p>
<h3>Hypercolor</h3>
<p><a href="https://hypercolor.dev/" title="Gradients for Tailwind CSS | Hypercolor"><a href="https://hypercolor.dev/">Gradients for Tailwind CSS | Hypercolor</a></a> 基于<code>tailwindcss</code>一系列绚丽的 CSS 渐近背景色代码和示例，你喜欢的话甚至可以直接下载作为壁纸。</p>
<h3>better-sqlite3</h3>
<p><a href="https://github.com/WiseLibs/better-sqlite3" title="WiseLibs/better-sqlite3: The fastest and simplest library for SQLite3 in Node.js."><a href="https://github.com/WiseLibs/better-sqlite3">WiseLibs/better-sqlite3: The fastest and simplest library for SQLite3 in Node.js.</a></a></p>
<p>这是一个速度快+易用性极高的<code>nodejs</code>库，用户操作<code>SQLite3</code>数据库。其主要特点如下：</p>
<blockquote>
<p>SQLite 依然是小型数据存储的最佳选择之一</p>
</blockquote>
<ul>
<li>完整的事务支持</li>
<li>高性能、效率和安全性</li>
<li>易于使用的同步API（比异步API更好的并发性...是的，你没看错）</li>
<li>支持用户定义的函数、聚合、虚拟表和扩展</li>
<li>64位整数（可选）</li>
<li>工作线程支持（用于大型/缓慢查询）</li>
</ul>
<h3>React Tooltip</h3>
<p><img src="https://files.mdnice.com/user/9850/a404ca27-6400-49b2-892c-5e3965949f1c.png" alt=""></p>
<p><a href="https://react-tooltip.com/" title="Welcome | React Tooltip"><a href="https://react-tooltip.com/">Welcome | React Tooltip</a></a>开箱即用的<code>React</code>提示框组件，外观简洁，代码非常易用！</p>
<h3>安妮薇看看</h3>
<p><img src="https://files.mdnice.com/user/9850/de38af38-e3bb-4c45-83c0-a80f38970387.png" alt=""></p>
<p><a href="https://anyway.fm/now/" title="安妮薇看看 Anyway.Now • Anyway.FM 设计杂谈 • UI 设计师 JJ 和 Leon 主播的设计播客 Podcast"><a href="https://anyway.fm/now/">安妮薇看看 Anyway.Now • Anyway.FM 设计杂谈 • UI 设计师 JJ 和 Leon 主播的设计播客 Podcast</a></a>！通勤的时候听到的播客，主要是两个设计师做的一个播客节目，提供与设计行业相关的新闻资讯、网摘推荐以及专栏文章等内容。</p>
<p>如果你从事 UI 和交互设计相关工作，或许对你有所帮助。</p>
<h3>字体分享</h3>
<p><img src="https://files.mdnice.com/user/9850/ed71cba1-3592-4576-b08a-0c694db23caa.png" alt=""></p>
<blockquote>
<p>如果你自己的应用想要换一个可商用的中文字体，不妨看看以下两个基于黑体和仿宋的变体字体，非常优美。</p>
</blockquote>
<ul>
<li><a href="https://github.com/welai/glow-sans" title="welai/glow-sans: SHSans-derived CJK font family with a more concise &amp; modern look. 未来荧黑·未來熒黑·ヒカリ角ゴ：基于思源黑体改造，拥有粗度和宽度系列，更加简明现代的超大字体家族。"><a href="https://github.com/welai/glow-sans">welai/glow-sans: SHSans-derived CJK font family with a more concise &amp; modern look. 未来荧黑·未來熒黑·ヒカリ角ゴ：基于思源黑体改造，拥有粗度和宽度系列，更加简明现代的超大字体家族。</a></a></li>
<li><a href="https://github.com/TrionesType/zhuque" title="TrionesType/zhuque: 朱雀仿宋：开源正文仿宋字库计划"><a href="https://github.com/TrionesType/zhuque">TrionesType/zhuque: 朱雀仿宋：开源正文仿宋字库计划</a></a></li>
</ul>
<p><img src="https://files.mdnice.com/user/9850/3d23b7bb-bea3-476f-a09d-1a52412a9e1a.png" alt=""></p>
<h3>Windows 11 有线网络自动断开问题</h3>
<p>最近发现我女朋友的 windows11 网络会自动断开，我查到是升级了系统之后才出现的这种情况。于是，我在网上查了一些资料，发现 Windows 11 的网络适配器有一个客户端设置，支持休眠时自动关闭适配器客户端以节省电量。😂 提一嘴~</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
<p>ps: 🎉 <code>Erin</code> 获得了<code>382</code>个用户！继续加油，下一个开源插件<code>TabX</code>在路上了！</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Infuse 平替应用 Vidhub ]]></title>
            <link>https://i5lin.top/blog/post/1851524180</link>
            <guid isPermaLink="false">1851524180</guid>
            <pubDate>Tue, 15 Aug 2023 14:17:57 GMT</pubDate>
            <description><![CDATA[Hello， 大家好。这里是妙才的周刊第 45 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀

![image](https://github.com/Developer27149/blog/assets/23721611/3a27084f-84f5-49fe-a3da-a172db82bd6c)



> 封面图：]]></description>
            <content:encoded><![CDATA[<p>Hello， 大家好。这里是妙才的周刊第 45 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/3a27084f-84f5-49fe-a3da-a172db82bd6c" alt="image"></p>
<blockquote>
<p>封面图：咪咪</p>
</blockquote>
<p>以下是本周分享的内容：</p>
<h3>Vidhub</h3>
<p><img src="https://static1.appinn.com/images/202308/appinn-feature-images-2023-08-07t151641-474.jpg!o" alt=""></p>
<p>Vidhub 是一款可以挂载<code>SMB</code>、<code>WebDAV</code>、<code>谷歌云盘</code>、<code>阿里云盘</code>、<code>百度网盘</code>等云盘服务的资源管理软件，其主打视频管理和媒体播放功能，目前仅支持<code>MacOS</code>客户端，其他客户端正在开发中。</p>
<p><img src="https://static1.appinn.com/images/202308/appinn-2023-08-07-15-30-042x.jpg!o" alt=""></p>
<p>笔者体验挂载了阿里云盘，查阅文件和播放视频都很方便，目前可以作为免费版的<code>Infuse</code>食用 😂</p>
<h3>Million</h3>
<p><img src="https://raw.githubusercontent.com/aidenybai/million/main/.github/assets/banner.png" alt="Million.js Banner"></p>
<p><a href="https://million.dev/">Million.js</a> 使用和<code>React</code>不同的虚拟<code>DOM</code>算法，仅需在原<code>React</code>组件外包装一层<code>million/react</code>的<code>block</code>函数，即可加速组件更新效率 <code>70%</code>!</p>
<blockquote>
<p>Github stars 超 12k</p>
</blockquote>
<h3>Html2canvas</h3>
<img width="1072" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/69135ba2-4697-4810-9290-8e483b54be10"><p><a href="https://html2canvas.hertzen.com/"><a href="https://html2canvas.hertzen.com/">html2canvas - Screenshots with JavaScript</a></a> 使用原生 JavaScript 对 DOM 元素节点进行截图操作，目前在 github 上有 28646 stars！</p>
<h3>阮一峰：TypeScript 教程</h3>
<p><img src="https://cdn.beekka.com/blogimg/asset/202308/bg2023080705.webp" alt=""></p>
<p><a href="https://wangdoc.com/typescript/"><a href="https://wangdoc.com/typescript/">TypeScript 教程 - 网道</a></a>：阮一峰发布了最新的 TypeScript 教程，欢迎 TypeScript 开发者学习。</p>
<h3>Web Browser Quiche</h3>
<p><a href="https://apps.apple.com/app/quiche-browser/id1668363952"><a href="https://apps.apple.com/app/quiche-browser/id1668363952">Web Browser Quiche on the App Store</a></a> IOS 极简移动端浏览器，极简爱好者强推。</p>
<h3>Distill</h3>
<p><a href="https://distill.io/"><a href="https://distill.io/">Monitor web for changes and get instant alerts</a></a> 网页监测服务，如果你需要一个服务来检测某些 web 页面的变更，那么这个应用会帮上忙。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[使用 Driverjs 快速创建新用户引导交互效果]]></title>
            <link>https://i5lin.top/blog/post/1838298983</link>
            <guid isPermaLink="false">1838298983</guid>
            <pubDate>Sun, 06 Aug 2023 18:23:24 GMT</pubDate>
            <description><![CDATA[Hello， 大家好。这里是妙才的周刊第 44 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀

![](https://files.mdnice.com/user/9850/cc4bc766-cc9d-4e9e-8ff7-0720fb324572.png)



> 封面图：我的小闪电~



以下是本周分享]]></description>
            <content:encoded><![CDATA[<p>Hello， 大家好。这里是妙才的周刊第 44 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀</p>
<p><img src="https://files.mdnice.com/user/9850/cc4bc766-cc9d-4e9e-8ff7-0720fb324572.png" alt=""></p>
<blockquote>
<p>封面图：我的小闪电~</p>
</blockquote>
<p>以下是本周分享的内容：</p>
<h3>Driverjs</h3>
<p><img src="https://files.mdnice.com/user/9850/29b35fda-2b3f-4c09-98af-8e66aa33cd44.png" alt=""></p>
<p><a href="https://driverjs.com/">Driver.js</a> 是一个用户提供产品用户引导交互效果的第三方 JavaScript 库，使用 TypeScript 编写并且无第三方依赖。如果你的产品需要做一个新手引导交互，或许可以考虑<a href="https://driverjs.com/">Driver.js</a>!</p>
<p>这个库的用法非常简单，直接使用<code>CDN</code>脚本或<code>npm</code>安装都可以，举个例子：</p>
<pre><code class="language-js">import { driver } from &quot;driver.js&quot;;
import &quot;driver.js/dist/driver.css&quot;;

const driverObj = driver({
  showProgress: true,
  steps: [
    { element: &#39;.page-header&#39;, popover: { title: &#39;Title&#39;, description: &#39;Description&#39; } },
    { element: &#39;.top-nav&#39;, popover: { title: &#39;Title&#39;, description: &#39;Description&#39; } },
    { element: &#39;.sidebar&#39;, popover: { title: &#39;Title&#39;, description: &#39;Description&#39; } },
    { element: &#39;.footer&#39;, popover: { title: &#39;Title&#39;, description: &#39;Description&#39; } },
  ]
});

driverObj.drive();
</code></pre>
<p>如此即可提供一个具有多个步骤的用户引导交互，其样式和动画笔者体验之后感觉还是不错的。</p>
<h3>Yazi</h3>
<p><a href="https://github.com/sxyazi/yazi">sxyazi/yazi: ⚡️ Blazing fast terminal file manager written in Rust, based on async I/O.</a> 基于<code>Rust Async I/O</code>开发的终端文件管理器！</p>
<p>支持终端搜索、文件预览、代码高亮等功能，速度很快，喜欢终端的朋友或许可以看看。</p>
<h3>Web Interface Guidelines</h3>
<p><a href="https://interfaces.rauno.me/">Web Interface Guidelines</a>：这份文档概述了构建一个良好（的网页）界面所需的细节，并且这是一个活跃的文档，根据学习经验定期更新。其中存在一些主观观念，但大多适用于所有网站。笔者在此真诚地分享这个文档给其他 web 开发者亦或是正在阅读的你。</p>
<h3>Vesper</h3>
<p><a href="https://marketplace.visualstudio.com/items?itemName=raunofreiberg.vesper">Vesper - Visual Studio Marketplace</a> 是一款暗色系的<code>VS Code</code>编辑器主题。</p>
<p><img src="https://cdn.rauno.me/vesper-og.png" alt=""></p>
<h3>Aplayer-react</h3>
<p><a href="https://aplayer-react.js.org/">aplayer-react – Nextra</a>：以<code>aplayer</code>为原型的<code>React</code>播放器组件实现，支持<code>SSR</code>。</p>
<p>作者将之应用到了自己的<code>Gatsby</code>博客中，效果如下：</p>
<p><img src="https://github.com/SevenOutman/aplayer-react/raw/master/screenshot.png" alt=""></p>
<p>对于开发者来说，我想我们或许可以从其源代码：<a href="https://github.com/SevenOutman/aplayer-react">SevenOutman/aplayer-react: The missing APlayer for your React applications</a> 之中学习到如何开发一个第三方的组件，以及一些良好的代码范式和编写方法。</p>
<h3>Ts-lib-starter</h3>
<p><a href="https://github.com/egoist/ts-lib-starter">egoist/ts-lib-starter: My minimal TypeScript library starter</a>：作者在 github 上分享了自己的最小化<code>TypeScript</code>库开发源代码，如果你对使用<code>TypeScript</code>开发一个库感兴趣，推荐阅读。</p>
<p>这个仓库的主要功能特色如下：</p>
<ul>
<li>使用<code>pnpm</code>管理包依赖</li>
<li>使用<code>semantic-release</code>管理自动版本发布</li>
<li>使用<code>tsup</code>打包<code>TypeScript</code>源代码</li>
<li>使用<code>Vitest</code>进行测试</li>
</ul>
<p>如果你对这部分知识感兴趣，欢迎和我一起阅读这个仓库的代码。</p>
<h3>MingCute Icon</h3>
<p><img src="https://files.mdnice.com/user/9850/4ea035ef-cff8-41a6-be32-d81b06cd9177.png" alt=""></p>
<blockquote>
<p>目前已经发布了 2400+ 免费 Icon 资源，支持下载<code>svg</code>或<code>png</code>文件，亦或是复制<code>svg</code>代码，并且支持一定的编辑功能。</p>
</blockquote>
<p><a href="https://www.mingcute.com/">MingCute Icon</a>是一个简单而精美的开源图标库。无论您是设计师还是开发人员，它都非常适合在Web和移动应用中使用。</p>
<h3>Get TT/X icons back</h3>
<p><a href="https://github.com/pionxzh/Get-Twitter-Icons-Back">pionxzh/Get-Twitter-Icons-Back: Restore the icon of Twitter. No more 𝕏.</a>!</p>
<p>众所周知，马斯克把<code>Logo</code>直接改了。于是乎有开发者写了这个油猴脚本来替换旧版<code>Logo</code>，如果你需要（眼不见为净），自取 😂</p>
<h3>书籍：经济旋涡</h3>
<p><a href="https://drive.google.com/file/d/1RdgJne7nLdr9x4Z5inywT_LWdfu12Nw7/view">https://drive.google.com/file/d/1RdgJne7nLdr9x4Z5inywT_LWdfu12Nw7/view</a> 谷歌云盘</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
<p>ps: 🎉 <code>Erin</code> 获得了<code>177</code>个用户~ 开心！</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[卓越的技艺需要刻意的练习]]></title>
            <link>https://i5lin.top/blog/post/1827977768</link>
            <guid isPermaLink="false">1827977768</guid>
            <pubDate>Sun, 30 Jul 2023 15:34:27 GMT</pubDate>
            <description><![CDATA[Hello， 大家好。这里是妙才的周刊第 43 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀

![image-20230730214430988](https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/21EiR3.jpg)



> 封面图：我的小闪电]]></description>
            <content:encoded><![CDATA[<p>Hello， 大家好。这里是妙才的周刊第 43 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/21EiR3.jpg" alt="image-20230730214430988"></p>
<blockquote>
<p>封面图：我的小闪电穿过前司发的日历📅</p>
</blockquote>
<p>以下是本周分享的内容：</p>
<h3>WeekTodo</h3>
<img width="1398" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/5f0cc377-197f-4ed9-bdd1-9c7513490f30"><p><a href="https://app.weektodo.me/">WeekToDo Planner</a>是一个开源的代办任务应用，其以一周为周期展示面板数据，不同日期的数据可以方便地拖动修改时间，动态修改状态、检索任务等等。</p>
<blockquote>
<p>这个应用的国际化做得非常好，支持众多语言。</p>
</blockquote>
<p>此外，还支持导入和导出数据，跨设备数据同步等。对于需要日常记录工作的朋友来说，还是非常实用的。</p>
<h3>React-hot-toast</h3>
<p><a href="https://react-hot-toast.com/">react-hot-toast - The Best React Notifications in Town</a>：号称最好的<code>React</code>通知组件，预览发现其<code>CSS</code>动画做得挺不错的，此外仅仅有<code>5kb</code>大小，还支持良好的异步逻辑，对于那些需要动态确定数据状态的场景很实用。</p>
<h3>JSZip</h3>
<p><a href="https://stuk.github.io/jszip/">JSZip</a>是一个 JavaScript 原生的库，提供简易清晰的<code>API</code>来支持<code>zip</code>文件压缩和解压缩的功能</p>
<h3>TinyVG</h3>
<p><img src="https://cdn.beekka.com/blogimg/asset/202303/bg2023032101.webp" alt=""></p>
<p><a href="https://tinyvg.tech/">TinyVG</a>SVG 图片格式的二进制版本，可以大大缩小 SVG 文件体积。</p>
<h3>Keypirinha</h3>
<p><a href="https://keypirinha.com/index.html">Keypirinha — Keypirinha</a> 是一款<code>Windows</code>下的文件、应用、浏览器书签快速搜索工具，类似<code>macOS</code>的<code>raycast</code>或<code>alfred</code>。</p>
<h3>Snipaste</h3>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230730221306586.png" alt="image-20230730221306586"></p>
<p><a href="https://zh.snipaste.com/">Snipaste - 截图 + 贴图</a> 超强工具，跨平台支持得非常好。</p>
<h3>Trim Video</h3>
<p><a href="https://online-video-cutter.com/">Video Trimmer - Cut Video Online for Free - MP4, AVI, MPG, 3GP</a> 在线屏幕录制和剪切工具，支持 200M 大小以内的文件操作。</p>
<h3>MediaInfo</h3>
<p><a href="https://mediaarea.net/en/MediaInfo">MediaInfo</a>是一个方便的统一显示视频和音频文件最相关技术和标签数据的工具。</p>
<h3>网易云音乐自动签到</h3>
<p><a href="https://github.com/Developer27149/net163-automatic-daily-signin">Developer27149/net163-automatic-daily-signin</a>：复制<code>Cookie</code>到<code>Github</code>仓库，设置对应的密钥，在<code>github action</code>定期调用脚本进行签到。</p>
<h3>简洁的 TypeScript 之书</h3>
<blockquote>
<p>《Concise TypeScript Book》全面而简洁地概述了 TypeScript 的功能。它提供了清晰的解释，涵盖了该语言最新版本中的所有方面，从强大的类型系统到高级功能。无论您是初学者还是经验丰富的开发人员，本书都是增强您对 TypeScript 的理解和熟练程度的宝贵资源。</p>
</blockquote>
<p><a href="https://github.com/gibbok/typescript-book/blob/main/README-zh_CN.md">typescript-book/README-zh_CN.md at main · gibbok/typescript-book</a>翻译了国外这本开源的电子书籍，非常适合前端开发者阅读学习以提高<code>TypeScript</code>语言的熟练度。</p>
<h3>有趣的言论</h3>
<ul>
<li>如果一个人见面就询问你谋生的方式，那么对方大概率在评估对你的尊重程度</li>
<li>卓越的技艺需要刻意地练习</li>
</ul>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[如何共享和检索终端命令历史]]></title>
            <link>https://i5lin.top/blog/post/1817198918</link>
            <guid isPermaLink="false">1817198918</guid>
            <pubDate>Sun, 23 Jul 2023 16:47:48 GMT</pubDate>
            <description><![CDATA[Hello， 大家好。这里是妙才的周刊第 42 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀

![248a9fde-b677-4de6-bce6-542e7580c823-min](https://github.com/Developer27149/blog/assets/23721611/09593590-f547-40]]></description>
            <content:encoded><![CDATA[<p>Hello， 大家好。这里是妙才的周刊第 42 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/09593590-f547-4088-8c25-e5d683fae9a0" alt="248a9fde-b677-4de6-bce6-542e7580c823-min"></p>
<blockquote>
<p>封面图：我的小闪电穿过前司发的日历📅</p>
</blockquote>
<p>以下是本周分享的内容：</p>
<h3>Atuin</h3>
<img width="962" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/013a124b-d040-4138-9397-cb49e82d6efd"><p><a href="https://atuin.sh/"><a href="https://atuin.sh/">Magical Shell History | Atuin</a></a> 是一款基于<code>Rust</code>语言开发的命令行工具，可以同步、搜索、备份<code>shell</code>历史命令。如果你是一个<code>shell</code>重度用户，别错过这个应用~</p>
<p>支持如下<code>shell</code>:</p>
<ul>
<li>zsh</li>
<li>bash</li>
<li>fish</li>
<li>nushell</li>
</ul>
<p>如果你想要同步数据，那么需要使用命令行去注册一个账户，否则无法在不同的设备中共享数据。如果你有安全性的担忧，那么也可以自己部署一个服务。</p>
<h3>1Clipboard</h3>
<p>如果你希望能随时查找最近复制的文本、文件、图片等内容，或许你需要一个剪贴板工具。在<code>macOS</code>系统下，推荐使用<a href="https://maccy.app/"><a href="https://maccy.app/">Maccy - macOS clipboard manager</a></a>，不过这个工具仅有<code>macOS</code>版本。</p>
<p>在 Windows 下我找到了替代品：</p>
<p><a href="https://1clipboard.io/"><a href="https://1clipboard.io/">1Clipboard - A universal clipboard managing app that makes it easy to access your clipboard from anywhere on any device.</a></a>：跨平台的剪贴板管理工具，支持<code>Windows</code>和<code>macOS</code>系统。</p>
<p>此外，这个工具是基于<code>Electron</code>和<code>Angular</code>构建的开源项目，你可以在<a href="https://github.com/wiziple/1Clipboard"><a href="https://github.com/wiziple/1Clipboard">wiziple/1Clipboard: A universal clipboard managing app that makes it easy to access your clipboard from anywhere on any device.</a></a>访问这个仓库，不过目前作者正打算更新技术栈，后续应该会重新发布一个新的仓库。</p>
<h3>Umami</h3>
<p><a href="https://umami.is/"><a href="https://umami.is/">umami</a></a>是一个开源的网站数据分析工具，这是一个谷歌分析的替代方案，主打一个易用性和开源。</p>
<p><img src="https://cdn.fliggy.com/upic/ScreenFlow.gif" alt=""></p>
<h3>Final2x</h3>
<p><a href="https://github.com/Tohrusky/Final2x"><a href="https://github.com/Tohrusky/Final2x">Tohrusky/Final2x: 2^x Image Super-Resolution ☢️</a></a> 跨平台的图片处理工具，可以使用多个模型将图像超分辨率增强到任意大小，以提高图像的分辨率和质量，使其更清晰和更详细。目前，它支持几种模型，如RealCUGAN、RealESRGAN、Waifu2x和SRMD。</p>
<p><img src="https://camo.githubusercontent.com/31265fc29461c525ed134a59bc05307db39fd71fb3345e2b83dc29a5e80b31a2/68747470733a2f2f73322e6c6f6c692e6e65742f323032332f30372f32302f685769345536424e65716175334d462e706e67" alt=""></p>
<p>如果你需要根据一些低画质的图片去搜寻高画质的版本，或许可以试试这个工具。</p>
<h3>⌘K</h3>
<p><img src="https://github.com/pacocoursey/cmdk/raw/main/website/public/og.png" alt=""></p>
<p><a href="https://cmdk.paco.me/"><a href="https://cmdk.paco.me/">Fast, composable, unstyled command menu for React — ⌘K</a></a>：快速、灵活、无样式的菜单搜索<code>React</code>组件，你可以将项目包装在其他组件中，甚至可以作为静态JSX。</p>
<p>使用这个无样式组件我们可以大大减少自己去设计、实现基础逻辑的编码时间，节省精力！</p>
<p>让我们把时间花在开发应用上来~</p>
<h3>Novu</h3>
<p><a href="https://novu.co/"><a href="https://novu.co/">Novu - The open-source notification infrastructure</a></a>：开源的通知基础设施系统，用于发送短信、邮件、Slack 和 PUSH 通知。</p>
<blockquote>
<p>你既可以使用对方提供的服务，也可以自己部署。</p>
</blockquote>
<p>在开发阶段使用免费额度即可，此外还给独立开发者提供了较多的免费服务。</p>
<p>如下所示是一个简单的邮件 SDK 示例：</p>
<pre><code class="language-js">import {Novu} from &#39;@novu/node&#39;;
const novu = new Novu(process.env.NOVU_API_KEY);
await novu.trigger(&#39;&lt;TRIGGER_NAME&gt;&#39;,
  {
    to: {
      subscriberId: &#39;&lt;UNIQUE_IDENTIFIER&gt;&#39;,
      email: &#39;john@doemail.com&#39;,
      firstName: &#39;John&#39;,
      lastName: &#39;Doe&#39;,
    },
    payload: {
      name: &quot;Hello World&quot;,
      organization: {logo: &#39;https://happycorp.com/logo.png&#39;,},
    },
  }
);
</code></pre>
<h3>IMageOptim</h3>
<p><img src="https://imageoptim.com/ImageOptim-app@2x.png" alt=""></p>
<p><a href="https://imageoptim.com/howto.html">ImageOptim — better Save for Web</a>：一个GUI界面的无损图像优化<code>macOS</code>工具。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[爸，我该怎么做？]]></title>
            <link>https://i5lin.top/blog/post/1810299734</link>
            <guid isPermaLink="false">1810299734</guid>
            <pubDate>Tue, 18 Jul 2023 16:34:09 GMT</pubDate>
            <description><![CDATA[Hello， 大家好。这里是妙才的周刊第 41 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀

![image](https://github.com/Developer27149/blog/assets/23721611/f64e24d5-f69f-46fa-97e3-64de25581fda)




> 封面]]></description>
            <content:encoded><![CDATA[<p>Hello， 大家好。这里是妙才的周刊第 41 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/f64e24d5-f69f-46fa-97e3-64de25581fda" alt="image"></p>
<blockquote>
<p>封面图：我的闪电</p>
</blockquote>
<p>以下是本周话题：</p>
<h3>Dad,how do I?</h3>
<p><img src="https://adayimg.com/wp-content/uploads/2020/05/409bc0e770f6a3c46272697bbc1f12cc.jpg" alt=""></p>
<p><a href="https://www.youtube.com/@DadhowdoI/about"><a href="https://www.youtube.com/@DadhowdoI/about"> Dad, how do I? - YouTube</a></a>：这个频道有超过四百六十万人订阅。频道作者叫<code>Rob</code>，他在十四岁后就失去了父亲。</p>
<p>在疫情肆虐的日子里，他开设了这个频道，不断上传自己的影片，这些影片介绍了很多很多在学校里不会教导，但是在日常生活中却非常实用的技能，例如：</p>
<ul>
<li>刮胡子</li>
<li>做简餐</li>
<li>修水管</li>
<li>煎鸡蛋</li>
<li>换自行车轮胎</li>
<li>...</li>
</ul>
<p>这个频道起名为<code>Dad,How do I?</code>，就像一份赠予那些家庭破裂或失去父母的孩子的礼物，透过视频，通过一件件生活中的小事，代替父亲的角色，教导他们独立自强。</p>
<h3>File Nesting updater</h3>
<p><img src="https://gw.alipayobjects.com/zos/k/hr/gdcxks.png" alt=""></p>
<p><a href="https://marketplace.visualstudio.com/items?itemName=antfu.file-nesting"><a href="https://marketplace.visualstudio.com/items?itemName=antfu.file-nesting">File Nesting Updater - Visual Studio Marketplace</a></a>：前端圈知名开发者托尼老师开发的<code>vscode</code>插件，可以将杂乱不堪的前端配置文件显示整齐一些。</p>
<p>注意：📢 这个插件仅仅是在编辑器文件目录区修改样式，并没有实际修改目录的结构。</p>
<p>如果你曾对前端诸多配置文件感到厌烦，并且你还是<code>vscode</code>用户，那么你或许可以尝试一下这个插件。</p>
<h3>Github readme stats</h3>
<p><a href="https://github.com/anuraghazra/github-readme-stats#show-more-languages">anuraghazra/github-readme-stats: :zap: Dynamically generated stats for your github readmes</a></p>
<p>如果你想在你的 README 中获取动态生成的 GitHub 统计信息，这个仓库可以帮到大忙！</p>
<h3>React-lines-ellipsis</h3>
<p><a href="https://github.com/xiaody/react-lines-ellipsis"><a href="https://github.com/xiaody/react-lines-ellipsis">xiaody/react-lines-ellipsis: Simple multiline ellipsis component for React.JS</a></a>：一款简单的<code>React</code>多行截断，显示查看更多按钮的组件。</p>
<h3>Compresspng</h3>
<p><a href="https://compresspng.com/zh/"><a href="https://compresspng.com/zh/">在线压缩PNG图像</a></a>：免费的在线图像压缩工具，相比 tinypng 5M 的文件大小限制，这个工具则仅限制最多一次上传 20 张图片，且无大小限制。</p>
<p>对于临时需要压缩一些大型截图非常有用，上传的数据会在一小时后清理掉。</p>
<p>同时也支持压缩：</p>
<ul>
<li>jpeg</li>
<li>gif</li>
<li>pdf</li>
</ul>
<h3>深入设计模式</h3>
<p><img src="https://refactoringguru.cn/images/patterns/content/index-design-patterns-2x.png" alt=""></p>
<p>图文并茂的<a href="https://refactoringguru.cn/"><a href="https://refactoringguru.cn/">免费在线学习代码重构和设计模式</a></a>！官方网站的书籍分享：</p>
<p>深入设计模式.pdf <a href="https://www.aliyundrive.com/s/H47VfoYs4NT">https://www.aliyundrive.com/s/H47VfoYs4NT</a> </p>
<h3>设计模式 - 语雀</h3>
<p><a href="https://www.yuque.com/ant-design/design-pattern"><a href="https://www.yuque.com/ant-design/design-pattern">设计模式</a></a>：语雀上的设计模式分析技术分享，完整地记录了产品设计中各种常见的需求和交互设计原理和方案，总共 26 个文档，近 4 万字。</p>
<p>对产品设计和用户交互有兴趣的朋友可以看看。</p>
<h3>Wormhole</h3>
<p><a href="https://wormhole.app/"><a href="https://wormhole.app/">Wormhole - 简单、私密的文件共享</a></a>：不限速、最大上传<code>10G</code>的文件，作者声称不会存储加密密钥，服务端无法解密上传的文件，也不关心用户上传的内容，并且这些内容将会在 24 小时后自动从服务器上删除。</p>
<p>如果你临时需要一个文件网络共享服务，可以使用<code>wormhole</code>！</p>
<h3>SendGrid</h3>
<p><a href="https://sendgrid.com/pricing/"><a href="https://sendgrid.com/pricing/">Pricing and Plans | SendGrid</a></a>：一天一百次免费额度的邮件服务<code>API</code>，轻度使用尚可。</p>
<p>同类型的还有<a href="https://elasticemail.com/"><a href="https://elasticemail.com/">Email Marketing Software. Mail Delivery API | Elastic Email</a></a>，网站做得很精美，宣传也不错。免费额度也是每天一百次。</p>
<h3>JS-Screen-Shot</h3>
<p><a href="https://www.kaisir.cn/js-screen-shot/"><a href="https://www.kaisir.cn/js-screen-shot/">js-screen-shot · likaia</a></a>：国人作者开发的原生 JavaScript 截图库，支持<code>commonjs</code>和<code>ESModule</code>以及<code>script</code>标签引入，创建实例后即可截图，支持<code>h2c</code>和<code>webrtc</code>两种模式。</p>
<p>此外，如果你是一个<code>vue3</code>用户，则可以直接使用其插件：<a href="https://www.npmjs.com/package/vue-web-screen-shot"><a href="https://www.npmjs.com/package/vue-web-screen-shot">vue-web-screen-shot - npm</a></a>。</p>
<h3>Railway</h3>
<p><a href="https://railway.app/"><a href="https://railway.app/">Railwayopen in new window</a></a> 是一个可免费使用的 Serverless 部署平台。我们可以快速的将自己的项目部署到 Railway 平台上，甚至可以部署一些常见的数据库服务。</p>
<p>目前来看，每个月具有 5 美元额度可用，部署一些小的服务没问题。</p>
<h3>岚</h3>
<p><a href="https://unsplash.com/collections/hkToSCaeZUE/%E5%B2%9A"><a href="https://unsplash.com/collections/hkToSCaeZUE/%E5%B2%9A">岚 | 72 best free wallpaper, background, outdoor and desktop photos on Unsplash</a></a>，我收藏的壁纸。</p>
<h3>最后</h3>
<p>咕咕咕...</p>
<p>鸽了两周，我离职又找到新的工作了，离开了上家公司之后歇了一段时间。后面就是跨省搬家来来回回之类的事情比较麻烦，不过总的来说非常开心，事情都办完了，还接了小闪电到家。</p>
<p>总之，周刊还是会继续发~</p>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[使用 Excalidraw 绘制具有手绘风格的白板图]]></title>
            <link>https://i5lin.top/blog/post/1773363679</link>
            <guid isPermaLink="false">1773363679</guid>
            <pubDate>Sun, 25 Jun 2023 16:18:20 GMT</pubDate>
            <description><![CDATA[Hello， 大家好。这里是妙才的周刊第 40 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀

![image-20230625235625020](https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230625235625020.png)
]]></description>
            <content:encoded><![CDATA[<p>Hello， 大家好。这里是妙才的周刊第 40 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230625235625020.png" alt="image-20230625235625020"></p>
<blockquote>
<p>封面图：6-18 大卖喵喵</p>
</blockquote>
<p>以下是本周话题：</p>
<h3>Excalidraw</h3>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230625164041981.png" alt="image-20230625164041981"></p>
<p><a href="https://excalidraw.com/">Excalidraw</a> 是一款手绘风格的白板工具，上手极其容易，画风活泼。对于经常要阐述抽象概念的人非常友好，借助这款工具可以画出轻松活泼的图示，提高趣味性。</p>
<p>此外，<code>Excalidraw</code>官方支持<code>PWA</code>模式，我们可以直接安装到本地使用。</p>
<blockquote>
<p><a href="https://link.juejin.cn/?target=https%3A%2F%2Fblog.excalidraw.com%2Fintroducing-excalidraw-plus%2F">excalidraw+</a>可完成团队、账号、数据在线存储等管理，你只需要为团队版付费。</p>
</blockquote>
<p>除了用来画图外，这款工具也适用于远程会议、上网课或者公众演示的场合。</p>
<h3>Day Progress</h3>
<p><a href="https://sindresorhus.com/day-progress">Day Progress — Sindre Sorhus</a>:展示在 MacOS 菜单栏中剩余的时间。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/screenshot1.80cd3c2a.jpg" alt=""></p>
<p>亦或</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/screenshot2.053fd13d.jpg" alt=""></p>
<h3>CSS 技术揭秘与实战通关</h3>
<p>掘金小册推荐，<a href="https://s.juejin.cn/ds/yNoddg4/">CSS技术揭秘与实战通关</a>! 虾皮高级前端技术专家 Chokcoco 编写的，质量非常高。另外，她还发了五折优惠码:<code>MUaL03Ap</code>~</p>
<blockquote>
<p><a href="https://s.juejin.cn/ds/yNoddg4/">https://s.juejin.cn/ds/yNoddg4/</a> 这是推广链接，点击购买小册的话也算是支持我了~</p>
</blockquote>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/5a1c0036-2580-4146-84bc-37fedb30971a" alt="image"></p>
<h3>Wechat Repair Tool</h3>
<p>通常在微信内打开链接默认会打开微信浏览器，如果你想改变这个行为，使用默认的浏览器打开链接，那么你可以这样做：</p>
<ul>
<li>在用户输入框内输入：<code>:recover</code>打开修复工具</li>
<li>勾选右下角配置区域的选项：<code>使用默认浏览器打开链接</code></li>
</ul>
<h3>Rapid API</h3>
<p><a href="https://paw.cloud/">RapidAPI for Mac – The most advanced API tool for Mac</a>：<code>RapidAPI for Mac</code> 是一个全功能的<code> HTTP</code> 客户端。它有一个漂亮的本地 macOS 界面来组合请求、检查服务器响应、生成客户端代码和导出 API 定义。</p>
<p>配合<a href="https://rapidapi.com/hub">API Hub - Free Public &amp; Open Rest APIs | Rapid</a>服务的真实世界<code>API</code> 食用更佳，大多数服务都提供免费额度，我们可以用来实现最基础的功能。</p>
<h3>Swift Programming Language 中文版</h3>
<p>苹果<code>Swift</code>语言的官方文档如下：</p>
<ul>
<li>原版：<a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/">The Swift Programming Language (5.9 beta) | Documentation</a></li>
<li>中文版：<a href="https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese">SwiftGGTeam/the-swift-programming-language-in-chinese: 中文版 Apple 官方 Swift 教程《The Swift Programming Language》</a></li>
</ul>
<h3>矢量图下载站</h3>
<p><a href="https://www.manypixels.co/gallery">Free to Use Clip Art Images &amp; Vector Illustrations | ManyPixels</a>：免费使用艺术图片和矢量图，2500+ 的免费资源。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230625171921866.png" alt="image-20230625171921866"></p>
<h3>Monica - 随时随地聊天</h3>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230625185751711.png" alt="image-20230625185751711"></p>
<p><a href="https://monica.im/desktop#installation">Monica | 您的 ChatGPT AI 智能助理</a>：现在已经出了桌面<code>APP</code>了，支持<code>MacOS</code>和<code>Windows</code>！</p>
<h3>Flash 保存计划</h3>
<blockquote>
<p>魔塔！</p>
</blockquote>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230625172555937.png" alt="image-20230625172555937"></p>
<p><a href="https://flash.zczc.cz/">Flash 保存计划</a>：是一个flash游戏发烧友创建的站点，收藏了很多八零后九零后喜闻乐见的flash游戏，想玩一下的都可以用浏览器访问去寻找一下少年的记忆。</p>
<h3>蜘蛛侠之纵横宇宙</h3>
<p><a href="https://movie.douban.com/subject/30391186/">蜘蛛侠：纵横宇宙 (豆瓣)</a>：电影推荐，真好看啊！18 年出了前作<a href="https://movie.douban.com/subject/26374197/">蜘蛛侠：平行宇宙 (豆瓣)</a>，强推先看平行宇宙！</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[保持好奇心使人年轻]]></title>
            <link>https://i5lin.top/blog/post/1763425181</link>
            <guid isPermaLink="false">1763425181</guid>
            <pubDate>Mon, 19 Jun 2023 12:19:59 GMT</pubDate>
            <description><![CDATA[Hello， 大家好。这里是妙才的周刊第 39 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀



![image-20230619201757304](https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230619201757304.pn]]></description>
            <content:encoded><![CDATA[<p>Hello， 大家好。这里是妙才的周刊第 39 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230619201757304.png" alt="image-20230619201757304"></p>
<blockquote>
<p>封面图：喵喵喵</p>
</blockquote>
<p>以下是本周话题：</p>
<h3>Vercel AI SDK</h3>
<p><a href="https://sdk.vercel.ai/docs"> Vercel AI SDK</a>：Vercel 公司开发的一个基于<code>openai</code>的编程 SDK ，用户可以轻松使用这个 SDK 来搭建后端和前端服务，构建基于<code>openai</code>的第三方应用。</p>
<p>并且，开发者可以直接<code>fork</code>github 上的<a href="https://github.com/vercel-labs/ai">vercel-labs/ai: Build AI-powered applications with React, Svelte, and Vue</a>这个仓库，在<code>vercel</code>后台构建应用来体验一个最简化版本的<code>openai</code>服务，与<code>chatGPT 3.5</code>模型进行对话。</p>
<blockquote>
<p>免费版不稳定，而且通常国内没法正常访问。</p>
</blockquote>
<p>使用这个仓库构建的应用不需要我们提供<code>api token</code>，如果是本地构建或自己搭建则需要在配置文件里提供<code>token</code>，并且官方提供了以下几个模板项目供大家参考：</p>
<ul>
<li><a href="https://github.com/vercel-labs/ai/tree/main/examples/next-openai">ai/examples/next-openai at main · vercel-labs/ai · GitHub</a></li>
<li><a href="https://github.com/vercel-labs/ai/tree/main/examples/sveltekit-openai">ai/examples/sveltekit-openai at main · vercel-labs/ai · GitHub</a></li>
<li><a href="https://github.com/vercel-labs/ai/tree/main/examples/next-huggingface">ai/examples/next-huggingface at main · vercel-labs/ai · GitHub</a></li>
<li><a href="https://github.com/vercel-labs/ai/blob/main/examples/nuxt-openai/README.md">ai/examples/nuxt-openai/README.md at main · vercel-labs/ai · GitHub</a></li>
</ul>
<p>如上所示，覆盖了<code>React</code>、<code>Vue</code>、<code>Svelte</code>三个主流前端开发框架。</p>
<h3>JSer Dev 博客</h3>
<p><a href="https://jser.dev/">JSer.dev</a>：宝藏博主，全英文博客。内容非常值得初中级Web 开发者学习。</p>
<h3>JSer Dev QuickCode 75</h3>
<blockquote>
<p>让我们面对现实，算法问题在求职面试中仍然很重要，所以我在<code>LeetCode</code>上进行练习。</p>
<p>我没有时间去攻克所有的问题，所以<code>Grind 75</code>是我想要专注的问题列表。但是仍然需要大量时间来练习。当我们完成这个列表后，我们可能会开始遗忘，至少对我来说是这样。</p>
</blockquote>
<p><a href="https://jser.dev/2023-05-21-quick-code-75/">Introducing QuickCode 75 - a companion app for Grind 75</a>：浅层算法核心的 75 道题。</p>
<h3>DoDD</h3>
<p><a href="https://chrome.google.com/webstore/detail/dodd/kgneifofomjghckaeelmgmmpaiaodpmi?hl=en-GB">DoDD</a>:基于好友的 TODO List 应用开发的 Chrome 浏览器插件。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230619201446191.png" alt="image-20230619201446191"></p>
<p>忽略里面头像展示不了的问题，这家伙突然不返回头像了，不关我事。</p>
<h3>v2ex-jobs</h3>
<p><a href="https://github.com/Developer27149/v2ex-jobs">Developer27149/v2ex-jobs: 一个chrome浏览器插件，随时查看最新的v2ex工作招聘咨询，支持按需检索</a></p>
<h3>爱发电</h3>
<p><a href="https://afdian.net/">爱发电 · 连接创作者与粉丝的会员制平台</a>：笔者看到尤雨溪在这里发布了赞助支持的信息。</p>
<p>要吃饭的嘛~支持一下开源社区的创作者们。如果你是一个开源创作者，或许可以在这里发布你的讯息，让得益于你的作品的人有一个简单的渠道支持一下你。</p>
<p>我觉得这是一件好事😂</p>
<h3>EasyDict</h3>
<p>跟我一样贫穷？买不起<code>bob</code>翻译？</p>
<p>那就用<code>EasyDict</code>代替。</p>
<p><img src="https://raw.githubusercontent.com/tisfeng/ImageBed/main/uPic/icon_512x512-1671278252.png" alt=""></p>
<p><a href="https://github.com/tisfeng/Easydict">tisfeng/Easydict: 一个简洁优雅的翻译词典 macOS App。开箱即用，支持离线 OCR 识别，支持有道词典，🍎苹果系统翻译，DeepL，谷歌，百度和火山翻译。A concise and elegant Dictionary and Translator macOS App for looking up words and translating text.</a></p>
<h3>Raycast 翻译</h3>
<p><a href="https://www.raycast.com/isfeng/easydict#install">Raycast Store: Easy Dictionary</a>：EasyDict 的 Raycast 插件版本，如果你是重度的 Raycast 用户，这个翻译插件足以代替其他翻译工具。</p>
<h3>Raycast 修改哈希值</h3>
<p><a href="https://www.raycast.com/isfeng/modify-hash">Raycast Store: Modify Hash</a>：快速修改文件哈希值、文件加密和解密。有时候我们需要将一些文件放到云盘上，为了避免直接被服务商查看到内容，或者根据 MD5 值快速查询、校验等你不想被关照的一切。</p>
<h3>ModernFlyouts</h3>
<p><a href="https://modernflyouts-community.github.io/">ModernFlyouts</a>是一款 Windows 下的音量控制软件，可以代替默认的系统音量配置 UI，颜狗必备。</p>
<blockquote>
<p><a href="https://www.microsoft.com/en-us/p/modernflyouts-preview/9mt60qv066rp&mode=mini">你甚至可以从 Microsoft 商店下载~</a></p>
</blockquote>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230618010240529.png" alt="image-20230618010240529"></p>
<h3>糖果梦 AI</h3>
<p><a href="https://ai.tgmeng.com/auth/register?inviteCode=GWXKZ54R9">糖果梦AI</a>：国内可用的 AI 服务，除此之外还有Midjourney绘图服务。</p>
<h3>Github 学生认证</h3>
<p><a href="https://githubedu.com/">Github 学生认证</a>：Github 学生认证可以免费使用 Github copilot 和一些其他服务，诸如 <code>tech</code>域名、<code>name</code>域名、<a href="https://githubedu.com/jetbrains">Jetbrains全家桶</a>等一系列服务，强烈推荐（也不能这么说，有钱还是支持一下正版吧各位）</p>
<blockquote>
<p>注意：不可避免的认证会有封号风险</p>
</blockquote>
<p><code>github copilot</code>绝对是开发者福音。</p>
<h3>AI 图片编辑应用</h3>
<p><a href="https://www.fotor.com/photo-editor-app/editor/text">AI图片编辑 - 使用AI智能优化照片 | Fotor</a>，如果你需要设计一些图片、广告内容、文章封面等等，这个应用或许可以帮上你。</p>
<h3>Pixlr E</h3>
<p><a href="https://pixlr.com/cn/e/">图像编辑器：Pixlr E-免费图像编辑工具</a></p>
<h3>Inter</h3>
<p><a href="https://rsms.me/inter/#features">Inter</a>是一种专为电脑屏幕精心设计的字体。Inter具有较高的<code>x</code>高度，以便于混合大小写和小写文本的可读性。<code>Inter</code>是一种具有多种<code>OpenType</code>功能的可变字体，例如上下文替代功能，可以根据周围字形的形状调整标点符号，斜杠零用于区分“<code>0</code>”和“<code>o</code>”，等宽数字等。</p>
<p>猎奇的朋友可以体验体验，开发者也可以将之换作自己的编辑器字体。</p>
<h3>VSCode Icon theme</h3>
<p><a href="https://github.com/miguelsolorio/vscode-symbols">miguelsolorio/vscode-symbols: A file icon theme for VS Code</a> ：一款新的<code>vscode</code>编辑器图标主题，颜狗们可以看看。</p>
<p><img src="https://github.com/misolori/vscode-symbols/raw/main/preview.png" alt=""></p>
<h3>TTime 翻译软件</h3>
<p><a href="https://ttime.timerecord.cn/">TTime官网 - 一款简洁、高效、高颜值的输入、截图、划词翻译软件</a>，堪比<code>EasyDict</code>和<code>Bob</code>~</p>
<h3>字幕库</h3>
<p><a href="https://zimuku.org/">字幕库(zimuku) -- 字幕下载网站</a>，我找电影字幕的时候看到的站点，资源非常全。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[使用 OrbStack 运行 MacOS 上的 Docker 镜像]]></title>
            <link>https://i5lin.top/blog/post/1709927370</link>
            <guid isPermaLink="false">1709927370</guid>
            <pubDate>Mon, 15 May 2023 11:48:12 GMT</pubDate>
            <description><![CDATA[Hello， 大家好。这里是妙才的周刊第 37 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀

![image](https://github.com/Developer27149/blog/assets/23721611/f7027ad2-bcf6-49fa-8c12-a9c6f35ec45a)


> 封面图：厦门]]></description>
            <content:encoded><![CDATA[<p>Hello， 大家好。这里是妙才的周刊第 37 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/f7027ad2-bcf6-49fa-8c12-a9c6f35ec45a" alt="image"></p>
<blockquote>
<p>封面图：厦门·园博苑·思念</p>
</blockquote>
<p>以下是本周话题：</p>
<h3>OrbStatck</h3>
<p><a href="https://orbstack.dev/">OrbStack · Fast, light, simple Docker &amp; Linux on macOS</a>：MacOS 上运行 <code>Docker</code>镜像的又一方案。</p>
<p><img src="https://orbstack.dev/_next/image?url=%2Fimg%2Fhero.png&w=3840&q=75" alt=""></p>
<blockquote>
<p>tips：作者还是学生，膜拜</p>
</blockquote>
<p>根据作者的描述，这个工具初始化速度是<code>Docker Desktop</code>的七倍左右，另外在<code>CPU</code>占用率上也远远比<code>Docker Desktop</code>小，网络和内存方面也大大领先，笔者正在体验中，目前感觉就是一个字：<code>快</code>。</p>
<p>非常推荐给大家~</p>
<h3>CSS Gradient</h3>
<p><a href="https://cssgradient.io/">CSS Gradient — Generator, Maker, and Background</a>：方便地生成 CSS 背景过度色和遮罩，另外他的博客也经常更新关于<code>CSS</code>的文章，质量很高。</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/48fc5b81-5823-4d33-b2fb-6a72350c7681" alt="image"></p>
<h3>Icon-Icons</h3>
<p><a href="https://icon-icons.com/">Free Icons PNG, ICO, ICNS and SVG</a>：免费的图片资源素材网站，在提供众多的图标素材外，亮点在于这里有非常全面的<code>MacOS</code>应用程序 Logo 图片资源，如果你需要替换默认的<code>Logo</code>，那么这里的素材非常适合你。</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/fa1a51cd-a743-4c8a-8a00-e2396bd84d81" alt="image"></p>
<h3>SigmaOS</h3>
<p><a href="https://beta.sigmaos.com/">SigmaOS</a>：旨在重新定义用户使用互联网的方式（看起来像是一个新的类<code>Arc</code>浏览器）</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/5bab1701-846e-40a5-b34b-c64c3bb73c25" alt="image"></p>
<h3>高图网</h3>
<p><a href="https://gaoimg.com/">高图网-免费无版权高清图片下载</a>：笔者实在是受不了<code>Unsplash</code>的速度（怪谁呢），找到了一个国内的可商用高清图片站点。</p>
<h3>codeimg</h3>
<p><a href="https://codeimg.io/">Codeimg.io</a>：快速生成美观的代码示例图片，可以快速制作马斯克的社交媒体软件上常见某些代码图示。</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/c41d644a-f2a0-4ae5-898a-a5d892c169c3" alt="image"></p>
<h3>AALocker</h3>
<p><code>IOS</code>系统上隐藏应用的绝佳工具，谁还没几个不想让人看到的 APP 了是吧，限时免费到 5 月 31 日。</p>
<p><img src="https://img.lancdn.com/landian/2023/05/98623-1.png" alt=""></p>
<h3>V2EX Polish</h3>
<p><a href="https://v2p.vercel.app/">V2EX Polish - 浏览器插件</a>，专为<code>V2EX</code>用户设计的浏览器插件，丰富的扩展功能带给你出色的体验。</p>
<p><code>V2EX</code>的<code>UI</code>实在是太久没更新了，设计性其实挺一般的，颜控速来体验一下这个插件，支持以下功能：</p>
<ul>
<li>界面美化</li>
<li>评论回复嵌套</li>
<li>热门回复展示</li>
<li>表情回复支持</li>
<li>长回复优化，折叠内容</li>
<li>内置主题列表</li>
<li>等等</li>
</ul>
<h3>React-If</h3>
<p><a href="https://romac.github.io/react-if/">react-if</a>：<code>React</code>的条件渲染解决方案，笔者正在使用。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[使用 CSS BEM 规范对样式进行命名]]></title>
            <link>https://i5lin.top/blog/post/1700587531</link>
            <guid isPermaLink="false">1700587531</guid>
            <pubDate>Mon, 08 May 2023 16:49:20 GMT</pubDate>
            <description><![CDATA[Hello， 大家好。这里是妙才的周刊第 35 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀

![](https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/X2zhVP.jpg)

> 封面图：象象的猫咪在巡视领地



以下是本周话题：

##]]></description>
            <content:encoded><![CDATA[<p>Hello， 大家好。这里是妙才的周刊第 35 期，这里将会分享笔者每周接触到的科技内容亦或是 Web 开发的一些新知识，甚至是一些有趣的非技术性人、事、物 🚀</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/X2zhVP.jpg" alt=""></p>
<blockquote>
<p>封面图：象象的猫咪在巡视领地</p>
</blockquote>
<p>以下是本周话题：</p>
<h3>Vue Toastification</h3>
<p><a href="https://vue-toastification.maronato.dev/">Vue-Toastification</a>：支持<code>vue2 和 vue3</code>的通知组件，扩展性很好，非常适合作为<code>vue</code>项目独立的消息通知组件。</p>
<p>以下是一些截图：</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/9dJh3q.png" alt="image-20230509002318298"></p>
<p>这个组件的动画效果还挺不错的，如果需要自定义一些样式也可以通过添加覆盖样式来满足需求。</p>
<h3>CSS BEM 命名规范</h3>
<blockquote>
<p><em>here are only two hard problems in Computer Science: cache invalidation and naming things — Phil Karlton</em></p>
</blockquote>
<p><a href="https://getbem.com/naming/">BEM — Naming</a>：BEM 方法确保参与网站开发的每个人都使用单一代码库并使用相同的语言。使用正确的命名将使您为网站设计的变化做好准备。</p>
<p>如果你需要开发团队组件库，亦或是开源组件库，在编写样式表的时候遵守良好的命名规范有利于后续的维护和提高代码的可读性。最重要的是，你将不必为给样式起名而烦恼。</p>
<p>如果你想看中文分享内容，可以看看这里<a href="https://juejin.cn/post/6844903672162304013">CSS — BEM 命名规范 - 掘金</a>。</p>
<h3>Valtio</h3>
<p><a href="https://valtio.pmnd.rs/">Valtio</a>是日本开发者<strong>dai-shi</strong>开发的又一<code>React</code>状态管理库，Valtio 使代理状态对 React 和 Vanilla 变得简单，笔者将会主力使用此库来处理状态数据操作。</p>
<h3>huntersofbook</h3>
<p><a href="https://opensource.huntersofbook.com/">huntersofbook the door from books to software · huntersofbook</a>：是专门为完善vue生态而建立的社区。作者声称自己的团队将永远把这种贡献回馈给世界。</p>
<p>主要分享：<code>vue3</code>、<code>Nuxt3</code>、<code>Vite4</code>、<code>TypeScript</code>、<code>Npm Packages</code>等技术相关的最佳实践。</p>
<h3><a href="https://ismail9k.github.io/vue3-carousel/">Vue3-carousel | Vue3-carousel</a></h3>
<p><a href="https://ismail9k.github.io/vue3-carousel/">Vue3-carousel | Vue3-carousel</a>是一个基于<code>Vue3</code>的轮播图组件库，代码示例很丰富，我们可以很方便地将之作为项目的轮播图解决方案。</p>
<p>文档内也详细地说明了对外暴露的一些接口和相关特性，入手难度很低。</p>
<h3>Flowbite</h3>
<p><a href="https://flowbite.com/">Flowbite - Build websites even faster with components on top of Tailwind CSS</a>：基于<code>tailwindcss</code>的<code>UI</code>组件库，样式调教得非常赏心悦目。如果你的项目用上了<code>tailwindcss</code>，那么就可以很方便地接入这个<code>UI</code>库。</p>
<p>此外，这个库更新非常频繁，目前已经有了超过 <code>600+</code> 的组件分享，扩展性也很棒。</p>
<h3>Vue3 + TS 搭建组件库</h3>
<p><a href="https://juejin.cn/post/7145113345765408798">Vue3 + TS 搭建组件库 - 掘金</a></p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7a9ad30199ee45a295645f6b907f0d5c~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp?" alt=""></p>
<p>内容很完整，可以作为自己的库的经验参考。</p>
<h3>Background Music</h3>
<p><a href="https://github.com/kyleneideck/BackgroundMusic">kyleneideck/BackgroundMusic: Background Music, a macOS audio utility: automatically pause your music, set individual apps&#39; volumes and record system audio.</a></p>
<p>这是一款<code>MacOS</code>的音乐调节软件，开源免费。</p>
<p>使用这个应用可以方便地单独调整系统每一个应用的音量🔊：<img src="https://github.com/kyleneideck/BackgroundMusic/raw/master/Images/README/Screenshot.png" alt=""></p>
<p>如果你也使用<code>brew</code>来管理应用，那么可以很方便地安装这个应用：</p>
<pre><code class="language-bash">brew install --cask background-music
</code></pre>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[每天问目前世界上最先进的人工智能模型一个学习的问题]]></title>
            <link>https://i5lin.top/blog/post/1683327642</link>
            <guid isPermaLink="false">1683327642</guid>
            <pubDate>Tue, 25 Apr 2023 14:50:28 GMT</pubDate>
            <description><![CDATA[每天免费向 chatGPT 4 问一个问题，你会询问它什么？

![](https://images.unsplash.com/photo-1675271591211-126ad94e495d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80)
]]></description>
            <content:encoded><![CDATA[<p>每天免费向 chatGPT 4 问一个问题，你会询问它什么？</p>
<p><img src="https://images.unsplash.com/photo-1675271591211-126ad94e495d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80" alt=""></p>
<p>现在是 2023 年 4 月 24 日，<code>chatGPT</code>已经将我国用户完全封锁了，除非我们使用一些非常规的手段来获得使用机会，否则几乎可以说国内用户已经和官方提供的服务绝缘了。</p>
<blockquote>
<p>+gpt 什么是 chatGPT 4 ?
ChatGPT 4 是指 Chat Generator Pre-trained Transformer 4，它是一个预训练的自然语言处理模型，旨在生成与人类对话相似的响应。ChatGPT 4 是 GPT-4 的一种变体，GPT-4 是 OpenAI 的下一代语言模型，目前已经发布。ChatGPT 4 可以用于聊天机器人、智能客服、语音助手等应用程序中，以帮助用户解决问题、提供信息和娱乐等。</p>
<p>Tips: 上面这句回答是这个世界上最先进的人工智能自然语言处理模型的v3版本针对我的提问返回的答案。</p>
</blockquote>
<p>在这样的情况下，我们怎么做才能最简单地拥有一次向<code>chatGPT 4</code>提问一次的机会，我们又该如何提问呢？</p>
<p>Hello， 大家好。这里是妙才的周刊第26期，本周要分享的内容如下：</p>
<h3>Poe</h3>
<p><a href="https://poe.com/">Sage - Poe</a>：（全称“开放探索平台”，Platform for Open Exploration）<strong>是一款由Quora开发的流动应用程式</strong>，于2022年12月推出。 该应用程式内置建基于人工智能技术的聊天机器人，可供用户向机器人询问专业知识、食谱、日常生活，甚或要求它创作文章等。</p>
<p>重点是支持中文且完全免费，每天有一次机会向<code>chatGPT4</code>或<code>Claude+</code>提问一次，并且向<code>Sage</code>模型、<code>chatGPT3.5</code>模型提问无限制，只要你可以想到都可以提问。</p>
<h3>Learning Prompt</h3>
<p><a href="https://learningprompt.wiki/">Hello from Learning Prompt | Learning Prompt</a> 是一个免费的 Prompt Engineering 教程，现在已经包含了免费的 ChatGPT 和 Midjourney 教程，学习这个教程可以让你更好地使用现今最火热的人工智能来解决现实问题。</p>
<h3>OpenPrompt</h3>
<p>你可以向<code>chatGPT</code>提任何问题，但这并不是免费的。每一个字符都具有价格，每一个问题都花费时间。如何高效地提问是一个很好的问题，在<a href="https://openprompt.co/">Open Prompt</a>你可以看到社区共享的一些<code>Prompt</code>（提问方式），或许能够对你组织自己的提问方式有所帮助。此外，这个站点目前可以每天免费提问10 条，使用体验也很不错。</p>
<h3>Ky</h3>
<p><a href="https://github.com/sindresorhus/ky">sindresorhus/ky: 🌳 Tiny &amp; elegant JavaScript HTTP client based on the browser Fetch API</a></p>
<p><img src="https://github.com/sindresorhus/ky/raw/main/media/logo.svg" alt=""></p>
<p><code>Ky</code>是一个基于<code>fetch api</code>的轻量化（无第三方依赖）的<code>http</code>客户端库，其面相的是现代浏览器（IE 吐槽的就是你）和<code>Deno</code>。</p>
<blockquote>
<p>非常值得学习源代码</p>
</blockquote>
<p>如果非要支持旧的浏览器，可以考虑使用<code>fetch polyfill</code>，如果需要支持<code>SSR</code>那么可以使用<a href="https://github.com/sindresorhus/ky-universal">sindresorhus/ky-universal: Use Ky in both Node.js and browsers</a>。</p>
<h3>AIOS Key</h3>
<p><a href="https://key.aios.chat/">AIOS Key</a>：国内开发者团队创建的基于<code>openai</code>官方接口的中转服务，如果你不想在自己花费大量时间构建自己的后台 API 系统，可以接入这个服务。目前注册免费送 10 万 token，我用来替换了 openCat 的官方<code>key</code>，感觉还不错。</p>
<h3>API 搬运工</h3>
<p><a href="https://github.com/fangzesheng/free-api">fangzesheng/free-api: 收集免费的接口服务,做一个api的搬运工</a>，如果你短暂地需要一个真正的 API，那么可以看看这个免费 API 收集列表。</p>
<h3>Vite-plugin-banner</h3>
<p><a href="https://github.com/chengpeiquan/vite-plugin-banner">chengpeiquan/vite-plugin-banner: A banner plugin for Vite. It can adds a banner to the top of each generated chunk.</a>可以让使用<code>vite</code>进行开发的朋友方便地给打包出来的文件添加描述信息，诸如版本号、作者联系方式、时间日期等等。</p>
<h3>UnoCSS</h3>
<p><a href="https://unocss.dev/">UnoCSS: The instant on-demand Atomic CSS engine</a>：按需即时的原子化 CSS 引擎，一切都在设计时考虑了灵活性和性能。 UnoCSS 中没有核心实用程序，所有功能都是通过预设提供的。 UnoCSS 的主要目标是直观性和可定制性。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[习惯是如此之轻，以至于无法察觉。又是如此之重，以至于无法挣脱。]]></title>
            <link>https://i5lin.top/blog/post/1642353092</link>
            <guid isPermaLink="false">1642353092</guid>
            <pubDate>Mon, 27 Mar 2023 15:44:48 GMT</pubDate>
            <description><![CDATA[习惯是如此之轻，以至于无法察觉。又是如此之重，以至于无法挣脱。 ——沃伦·巴菲特

![image](https://user-images.githubusercontent.com/23721611/227992877-1ca19b1c-867e-4a00-8109-89f2c238dfed.png)


Hello， 大家好。这里是妙才的周刊第`31`期，本周要分享其他的内容如下：]]></description>
            <content:encoded><![CDATA[<p>习惯是如此之轻，以至于无法察觉。又是如此之重，以至于无法挣脱。 ——沃伦·巴菲特</p>
<p><img src="https://user-images.githubusercontent.com/23721611/227992877-1ca19b1c-867e-4a00-8109-89f2c238dfed.png" alt="image"></p>
<p>Hello， 大家好。这里是妙才的周刊第<code>31</code>期，本周要分享其他的内容如下：</p>
<h3>哈佛大学Python课程</h3>
<p><img src="https://user-images.githubusercontent.com/23721611/227992904-3903e777-5c72-400a-a2c8-1799a06bab52.png" alt="image"></p>
<p><a href="https://cs50.harvard.edu/python/2022/weeks/0/">Week 0 Functions - CS50&#39;s Introduction to Programming with Python</a>：考不上哈佛没关系，这里有一份哈佛著名的 CS 50 计算机科学入门课程之一：Python 编程介绍。既可以练听力又能学 Python ！一举两得，希望你喜欢！</p>
<blockquote>
<p>CS50是哈佛大学的一门计算机科学入门课程，旨在教授学生计算机科学的基本概念和编程技能。该课程涵盖了计算机科学的广泛领域，包括算法、数据结构、计算机系统、网络、安全性和人工智能等方面。CS50课程由哈佛大学教授David J. Malan主讲，并且已经成为全球最受欢迎的在线计算机科学课程之一。</p>
</blockquote>
<h3>堆友 - 3D 设计在线编辑器</h3>
<p><a href="https://d.design/">堆友—3D在线编辑器，0门槛堆出无限创意，免费下载，免费使用</a>：素材挺好看的，下载需要消耗一定的<code>堆豆</code>，官方提供 120 个堆豆。</p>
<img width="497" alt="image" src="https://user-images.githubusercontent.com/23721611/227992932-45981315-1247-4f4c-ba21-b29b5f4ffa75.png"><h3>Linux 文件系统九讲</h3>
<p><a href="https://www.youtube.com/playlist?app=desktop&list=PL3wVcVGXqdnbp8ww_ogY39Mnk8IVNE3B1">Linux 文件系统九讲 - YouTube</a>：九个 Linux 文件系统视频，质量很高。</p>
<h3>Free Tailwind Tutorials</h3>
<p><a href="https://www.protailwind.com/tutorials">Free Tailwind Tutorials from Simon Vrachliotis</a>：免费的 tailwind 入门教程，使用了很可爱的图示例子，很有趣。</p>
<h3>DrawKit - 手绘矢量图资源</h3>
<p><a href="https://www.drawkit.com/">DrawKit - Beautiful vector illustrations</a>：手绘矢量插图和图标资源，非常适合您的下一个项目。</p>
<h3>Matt Pocock&#39;s TypeScript video tutorials</h3>
<p><a href="https://www.youtube.com/playlist?app=desktop&list=PLIvujZeVDLMx040-j1W4WFs1BxuTGdI_b">(1) Advanced TypeScript - YouTube</a>： 油管上非常出名的 TypeScript 系列教学视频，既可以学英语还可以学<code>TypeScript</code>，双赢！</p>
<h3>myGPTReader</h3>
<p><a href="https://github.com/madawei2699/myGPTReader">madawei2699/myGPTReader: myGPTReader is a slack bot that can read any webpage, ebook, video(YouTube) or document and summarize it with chatGPT. It can also talk to you via voice using the content in the channel.</a></p>
<p>如果你想在<code>slack</code>上部署一个可以读取文档、电子书、YouTube 视频，甚至是语音对话的机器人，可以参考这篇文档进行部署。</p>
<h3>GOST</h3>
<p><a href="https://github.com/ginuerzh/gost">ginuerzh/gost: GO Simple Tunnel - a simple tunnel written in golang</a>： Go 实现的极简信道通信工具，可以用来部署自由网络。</p>
<h3>xLog</h3>
<p><a href="https://xlog.app/">xLog</a>：面相所有人的链上开源博客社区，时间线上的文章很有趣。能懂得在这里写东西的人在这个社会中实在是少数中的少数，推荐看看这里的文章开阔自己的视野。</p>
<h3>Bilix - 最好用的 B 站下载器</h3>
<p><a href="https://oldcat.cf/Bilix">【软件推荐】Bilix - 最好用的B站下载器 - oldcatY</a>，本文介绍了两款视频下载软件：BBdown和Bilix。BBdown的优点包括多线程下载和调用Aria2实现超高速下载，但Cookie容易被拉黑。Bilix是更加简易的超高速下载软件，优点包括使用pip包管理器安装、下载界面直观、无需Aria2c即可实现超多线程下载等。需要手动扒取Cookie，但对于需要大量扒取B站视频的用户来说，Bilix是最佳选择。本文介绍了如何配置Bilix，并提供了安装Bilix和配置下载命令的具体步骤。最后介绍了如何使用Bilix下载高清视频。</p>
<h3>土耳其订阅 ICloud+ 方案</h3>
<p><a href="https://zblogs.top/how-to-subscribe-to-turkey-icloud/">[最新教程] 国内用户订阅土耳其区 iCloud+ 攻略 - Justin写字的地方</a>：笔者看着这篇文章完成了土区账号拉入家庭共享，让自己的 Mac ICloud 具有了 2T 空间，以后可以自由地使用苹果生态的存储了！</p>
<h3>Chatbot - ui</h3>
<p><a href="https://github.com/mckaywrigley/chatbot-ui">mckaywrigley/chatbot-ui: An open source ChatGPT UI.</a>：如果想让国内的朋友体验到 openAI ，其实可以部署类似 <code>chatbot-ui</code> 这样的项目，只需要在部署的时候提供一个<code>token</code>即可本地访问，也可以部署在香港<code>vps</code>上让国内的朋友访问。</p>
<p><img src="https://github.com/mckaywrigley/chatbot-ui/raw/main/public/screenshot.png" alt="Chatbot UI"></p>
<p>本项目基于<code>nextjs</code>和<code>tailwindcss</code>以及<code>TypeScript</code>技术栈，想要学习大神的源代码的朋友也可以看看。</p>
<h3>MPV 播放器</h3>
<p>windows 下我一直用<code>potplayer</code>，但毕竟是商业软件要赚钱的。如果你也在寻找一个替代品，可以看看 <code>MPV</code>，<a href="https://github.com/dyphire/mpv-config">https://github.com/dyphire/mpv-config</a> 这里有一份配置文件和整合包，足够用了。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[机智是将世事人心放在X光线下照透，幽默则不带这种超越的态度，而似乎把人都看成兄弟，大家都有短处。]]></title>
            <link>https://i5lin.top/blog/post/1633204833</link>
            <guid isPermaLink="false">1633204833</guid>
            <pubDate>Tue, 21 Mar 2023 04:30:04 GMT</pubDate>
            <description><![CDATA[老舍的《谈幽默》：机智是将世事人心放在X光线下照透，幽默则不带这种超越的态度，而似乎把人都看成兄弟，大家都有短处。

![image](https://user-images.githubusercontent.com/23721611/226517380-9cc09693-2d19-445f-9d79-3e56466e6200.png)



嘿嘿！大家好，新的一周又开始了，下面是上]]></description>
            <content:encoded><![CDATA[<p>老舍的《谈幽默》：机智是将世事人心放在X光线下照透，幽默则不带这种超越的态度，而似乎把人都看成兄弟，大家都有短处。</p>
<p><img src="https://user-images.githubusercontent.com/23721611/226517380-9cc09693-2d19-445f-9d79-3e56466e6200.png" alt="image"></p>
<p>嘿嘿！大家好，新的一周又开始了，下面是上周的周刊内容分享：</p>
<h3>人工智能代码建议</h3>
<p><a href="https://codeium.com/">Codeium</a>：免费的人工智能代码建议插件，支持的平台很多：</p>
<p><img src="https://user-images.githubusercontent.com/23721611/226517404-5dc34fc4-3a31-42dd-a8ae-79f943be15cd.png" alt="image"></p>
<p>如果你喜欢 github 的 <code>copilot</code>却像找一个免费的人工智能代码工具，非常推荐试试这个工具！</p>
<h3>VS Code 和 chatGPT 4</h3>
<p><a href="https://github.com/mpociot/chatgpt-vscode">mpociot/chatgpt-vscode: A VSCode extension that allows you to use ChatGPT</a>：免费的<code>vscode</code>插件，不需要<code>OpenAI</code>密钥就能使用现在最火热的人工智能模型来辅助编程！</p>
<p>你可以询问问题，甚至是基于代码进行提问、review 建议、优化等。目前<code>chatGPT3.5</code>无限制，<code>chatGPT4</code>限制了访问的数量和速率，但是基本不影响使用。</p>
<p><img src="https://user-images.githubusercontent.com/23721611/226517427-97e5cd47-a043-46cf-8b61-99c4f8239a85.png" alt="image"></p>
<h3>AI CLI</h3>
<p><img src="https://user-images.githubusercontent.com/23721611/226517436-da6a3a62-7395-4709-b5b7-8d63261f8e3c.png" alt="image"></p>
<p><a href="https://github.com/abhagsain/ai-cli">abhagsain/ai-cli: Get answers for CLI commands from GPT3 right from your terminal</a>：基于<code>GPT3</code>的 CLI 工具，使用<code>nodejs</code>构建。具有<code>nodejs</code>环境的用户可以用<code>npm i -g @abhagsain/ai-cli</code>进行安装，然后直接询问即可，支持中文：</p>
<pre><code class="language-bash">$ ai ask &quot;Check process running on port&quot;
</code></pre>
<h3>扩展管理器</h3>
<p><a href="https://chrome.google.com/webstore/detail/extension-manager/gjldcdngmdknpinoemndlidpcabkggco">扩展管理器（Extension Manager）</a>：chrome 浏览器的扩展管理器，可以很方便的管理插件，UI 还挺美观的。</p>
<h3>chathub</h3>
<p><a href="https://chrome.google.com/webstore/detail/chathub-all-in-one-chatbo/iaakpnchhognanibcahlpcplchdfmgma">chathub</a>：chrome 浏览器插件：bing 和 chatgpt 二合一的客户端，前者需要登录必应，后者需要提供 Key 即可使用。</p>
<h3>Bing Unchained - use new bing in chrome</h3>
<p><a href="https://chrome.google.com/webstore/detail/bing-unchained-use-new-bi/laldfnbbeocphnilnofhedhcjcnchbld">Bing Unchained</a>：可以让你在 chrome 中使用 new bing!如果你拥有了<code>new bing</code>的权限，并且想在<code>chrome</code>上使用<code>new bing</code>，这个插件真是太赞了~</p>
<h3>LASTPASS - 密码管理解决方案</h3>
<p><a href="https://www.lastpass.com/">#1 Password Manager &amp; Vault App with Single-Sign On &amp; MFA Solutions | LastPass</a>：chrome 用户不容错过的免费密码管理插件，个人版已经足够用了，可以非常方便地管理密码。</p>
<h3>最后</h3>
<p>上周鸽了，想起朋友说要坚持，惭愧惭愧~下周再见，期待四月（ ！</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[不要让你的热情被磨灭，去创作一些什么亦或是去爱上些什么。]]></title>
            <link>https://i5lin.top/blog/post/1607269473</link>
            <guid isPermaLink="false">1607269473</guid>
            <pubDate>Thu, 02 Mar 2023 18:11:08 GMT</pubDate>
            <description><![CDATA[不要让你的热情被磨灭。

![image](https://user-images.githubusercontent.com/23721611/222515046-00964eae-4cc9-468b-ac2a-a0741f89fedf.png)


我本想在本周一发布本周的分享内容，但是周一开始就被耽搁了，直到今天23:58:30才开始动笔，糟糕的经历。

好了，本周的周刊分享如]]></description>
            <content:encoded><![CDATA[<p>不要让你的热情被磨灭。</p>
<p><img src="https://user-images.githubusercontent.com/23721611/222515046-00964eae-4cc9-468b-ac2a-a0741f89fedf.png" alt="image"></p>
<p>我本想在本周一发布本周的分享内容，但是周一开始就被耽搁了，直到今天23:58:30才开始动笔，糟糕的经历。</p>
<p>好了，本周的周刊分享如下：</p>
<h4>PopClip for Mac</h4>
<p><a href="https://pilotmoon.com/popclip/">PopClip for Mac</a> 是一个 MacOS 应用程序，当你使用在系统上选中某些文本之后，为系统增加一个增强性的功能。举个例子，当我们选中一段文字之后，<code>popClip</code>将会调用其功能来展示一些功能选项在选择的文本上方（你可以调整这个位置），这样一来就可以实现很多自定义的功能，举个我用到的例子：选择文本，弹出复制、粘贴、搜索、询问 chatGpt 疑问等一系列功能？你想要的是什么功能呢？或许可以在<code>PopClip</code>的插件列表这里找到你需要的插件，亦或者自己编写插件。</p>
<h4>Typewind</h4>
<p><a href="https://typewind.dev/">Typewind – Typesafe Tailwind</a> 是一个为<code>tailwindcss</code>提供基于<code>TypeScript</code>类型安全功能的辅助库。</p>
<img width="961" alt="image" src="https://user-images.githubusercontent.com/23721611/222515099-64eb6f85-04ab-484a-8a01-42ac04bc17a0.png"><p>良好的代码补全功能，将<code>tailwindcss</code>以另一种方式在构建时生成完整的<code>css</code>文件。以我个人的视角来看，这种链式书写的方式很棒。</p>
<p>另外，这个库是框架无关的，我们可以将之轻松融合到现有的技术栈里，也就是说我可以同时在<code>Vue</code>和<code>React</code>中无缝切换地使用。</p>
<h4>Planetscale</h4>
<p><a href="https://planetscale.com/">PlanetScale: The world’s most advanced database platform</a> ： 就像是<code>Planetscale</code>自己描述的那样，这是一个非常优秀的<code>serverless</code>数据平台，用户在不付费的情况下可以创建一个组织，平台提供5GB的存储空间，每个月10亿行读取和1000万行写入，还支持一个开发分支和一个生产分支，并且平台还有一个不错的社区，作为一个想要为自己的创作试用<code>serverless</code>服务的开发者，免费额度已经足够了。</p>
<h4>WallHaven</h4>
<p><a href="https://wallhaven.cc/">Awesome Wallpapers - wallhaven.cc</a> 是一个壁纸分享站点，用户可以免费下载超高清的壁纸，内容非常丰富，搜索做得非常灵活，非常适合喜欢壁纸的朋友。</p>
<blockquote>
<p>开发者也能通过其开放接口访问，速度上感觉比<code>unsplash</code>更好一些。</p>
</blockquote>
<p>举个例子：</p>
<p><img src="https://w.wallhaven.cc/full/zy/wallhaven-zyxvqy.jpg" alt=""></p>
<h4>Clack</h4>
<p>使用<a href="https://www.clack.cc/">Clack</a> 和<code>TypeScript</code>可以开发精美的<code>CLI</code>程序。</p>
<img width="599" alt="image" src="https://user-images.githubusercontent.com/23721611/222515205-a233c26f-07a7-49c5-965e-819aa320049d.png"><h4>防御性CSS</h4>
<p><a href="https://defensivecss.dev/">Defensive CSS</a> 有助于构建面向未来的用户界面的实用 CSS 和设计技巧，学习这个站点关于CSS的一些防御性知识，可以让你的CSS更加健壮。</p>
<p>最后，给大家推荐一个油管视频主：</p>
<p><img src="https://user-images.githubusercontent.com/23721611/222515260-d21ed5fc-c23b-4521-ac77-5770627c7e83.png" alt="image"></p>
<p><a href="https://www.youtube.com/@yorke">孫沁岳</a>：漫画、人生漫漫谈。</p>
<p>❤️ 吴林</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[微软收购OpenAI，给Bing搜索注入生命力]]></title>
            <link>https://i5lin.top/blog/post/1581281839</link>
            <guid isPermaLink="false">1581281839</guid>
            <pubDate>Sun, 12 Feb 2023 14:34:33 GMT</pubDate>
            <description><![CDATA[所有人都在讨论`chatgpt`，微软这波真的赢麻了。
![](https://images.unsplash.com/photo-1676299081847-824916de030a?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D)]]></description>
            <content:encoded><![CDATA[<p>所有人都在讨论<code>chatgpt</code>，微软这波真的赢麻了。
<img src="https://images.unsplash.com/photo-1676299081847-824916de030a?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt=""></p>
<p>微软收购<code>OpenAI</code>，并且准备给bing搜索注入<code>chatgpt</code>的力量，想想就令人激动。</p>
<p>如果你想提前体验，可以在这里排队：<a href="https://www.microsoft.com/en-us/edge/bing?form=MA13FJ">Microsoft Edge</a>。</p>
<p>此外，谷歌在演示自己的<code>Bard</code>这款人工智能竞品时出现事实性错误，导致股价蒸发💰💰💰😂，所以说微软真的赢麻了。</p>
<p>Hello， 大家好。这里是妙才的周刊第26期，本周要分享其他的内容如下：</p>
<h3><a href="https://midjourney.com/">Midjourney</a></h3>
<p><a href="https://midjourney.com/">Midjourney</a>是一个独立的研究实验室，其人工智能程序可以根据用户提供的文本描述创建图像，目前免费可用。进入其<code>Discord</code>之后，随便选择一个频道即可输入<code>image</code>命令和你的描述，即可排队生成相关图片。我有看到别人为自己的播客生成用户推广的宣传图片，效果还不错。</p>
<p>看看这个小女孩的例子，其生成描述为：“<strong>Avatar, girl, cartoon caracters ,Face light ,no accessories ,sunset, no blur,depth of field,Small-catchlight,cinematic colors , realistic,random AI 8k--V 4 --ar 3:2</strong>”：</p>
<p><img src="https://media.discordapp.net/attachments/1008571225309728878/1074320060698607758/elk_Avatar_girl_cartoon_caracters_Face_light_no_accessories_sun_7db18df9-d8d0-4383-848c-4c3f98361dc3.png" alt=""></p>
<p>如果你是内容创作者，你可以轻而易举地获得于内容搭配的超高清图片资源。</p>
<h3>Screenstab</h3>
<p><a href="https://www.screenstab.com/editor/">Screenstab</a>可以将一张平面图片，转换为具有一定倾斜度的设计性图片。还是以为上面那张小女生的图片为例，上传后即可生成如下图片（还可以进行角度微调）：</p>
<img width="1132" alt="image" src="https://user-images.githubusercontent.com/23721611/218317291-233799ca-a4da-4bcf-8a7e-440cb8658f54.png"><p>非常适合设计师，或者是独立开发者为自己的作品做宣传内容使用，以往自己进行处理可能需要花费很多时间和精力。</p>
<h3>ONE LIB</h3>
<p><a href="https://1lib.ink/">ONE LIB - 知识就是力量</a>：免费电子书下载，搜索于 Z-Library 数据库。</p>
<h3>LocalSend</h3>
<p><strong><a href="https://localsend.org/">LocalSend</a></strong> 是一款免费、开源、跨平台的局域网文件互传工具，无需互联网，无需外部服务器。即开即用，支持 Windows、macOS、Linux，以及 iOS、Android。</p>
<h3>He3</h3>
<p><a href="https://he3.app/zh/">He3: 开发者必备的万能工具箱</a>，没必要再在浏览器上找什么在线工具了，这个软件一把梭，随时用随时切。</p>
<p><img src="https://he3app.com/section/bg10.png" alt=""></p>
<p>此外，作者还为此创建了一个交流论坛：<a href="https://community.he3.app/">He3 App</a>，再也不怕找不到人了。</p>
<h3>Google Style Guides</h3>
<p><a href="https://google.github.io/styleguide/">Google Style Guides | styleguide</a>是谷歌开发者规范的共享文档，如果你对自己或团队的代码有规范设定参考的需求，可以参考一下这里。亦或是想学习一下世界上最好的公司的开发规范，也很值得一看。</p>
<h3>前端问问 - BigFrontEnd.dev</h3>
<p><a href="https://bigfrontend.dev/">BFE.dev - prepare for Front-End job interviews.</a>：了解这个站的所有问题和答案，会让你的前端开发水平直接上天。</p>
<h3>Icons8</h3>
<p><a href="https://icons8.com/">Free Icons, Clipart Illustrations, Photos, and Music</a>是一个<code>icon</code>图标聚合站点，其提供了大量精美的<code>icon</code>图标，尺寸设置和类型设置非常方便，很多资源是免费的，值得收藏。</p>
<h3>Peo</h3>
<p><a href="https://apps.apple.com/app/id1640745955">Poe – Fast, Helpful AI Chat on the App Store</a>：非国区可下载的人工智能对话APP，免费可用堪比<code>chatgpt</code>（仅支持iOS），暂时用不到<code>chatgpt</code>的朋友可以试试<code>Peo</code>。</p>
<h3>tremor</h3>
<p><a href="https://www.tremor.so/">Tremor – The React library to build dashboards fast</a>：基于<code>React+Tailwindcss+rechats</code>的组件库，用于快速构建后台看板系统。制作十分精美，样式非常好看，而且源代码非常简洁，不管是用还是学都非常有价值。</p>
<h3>一次性Gmail</h3>
<p><a href="https://www.emailnator.com/">Temporary Disposable Gmail | Temp Mail | Email Generator</a> 是一个提供一次性Gmail临时邮箱的站点，某些时候注册使用的临时邮箱会被限制为仅支持常规邮箱，这时候或许这个站能帮到你。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[我要不要用弹性布局的`gap`属性？]]></title>
            <link>https://i5lin.top/blog/post/1581235988</link>
            <guid isPermaLink="false">1581235988</guid>
            <pubDate>Sun, 12 Feb 2023 12:14:27 GMT</pubDate>
            <description><![CDATA[上班时我坚决不用，下班之后全都要用！

![](https://images.unsplash.com/photo-1634838083208-ce7a36701fe4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80)

今天看到群里的LSP吐]]></description>
            <content:encoded><![CDATA[<p>上班时我坚决不用，下班之后全都要用！</p>
<p><img src="https://images.unsplash.com/photo-1634838083208-ce7a36701fe4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2940&q=80" alt=""></p>
<p>今天看到群里的LSP吐槽了一下<code>flex</code>的<code>gap</code>属性，他的原话是这样说的：</p>
<p>LSP： &quot;嗝屁了这么差的。&quot;</p>
<p>掏粪工：“不是还可以啊”</p>
<p>LSP：“灾难。”</p>
<p>群友的对话成功吸引到了我的注意，于是我看了看群里发的图，思考了标题这个问题：“我要不要用弹性布局的<code>gap</code>属性？”</p>
<h3>过程</h3>
<p>在讨论要不要用之前，我们先来看看什么是<code>gap</code>属性：</p>
<blockquote>
<p>The gap property in CSS is a shorthand for <code>row-gap</code> and <code>column-gap</code>, specifying the size of gutters, which is the space between rows and columns within <a href="https://css-tricks.com/snippets/css/complete-guide-grid/">grid</a>, <a href="https://css-tricks.com/almanac/properties/f/flex/">flex</a>, and <a href="https://css-tricks.com/almanac/properties/c/columns/">multi-column</a> layouts.</p>
</blockquote>
<p>简而言之，就是弹性布局中子项主轴和交叉轴之间的空隙值。</p>
<p>举个例子<a href="https://css-tricks.com/almanac/properties/g/gap/">gap | CSS-Tricks - CSS-Tricks</a>：</p>
<pre><code class="language-css">
/* Grid layout */
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 1fr 2fr 1fr;
  gap: 30px 20px;
}

/* Flex layout */
.container {
  display: flex;
  gap: 10%;
}

/* Multi-column layout */
.container {
  column-count: 5;
  gap: 20px;
}
</code></pre>
<p>解释这个属性不是我想表达的重点，你可以在网上找到无数的解释，让我们回过头来说一说到底该不该用。</p>
<p>首先，来看看兼容性：</p>
<p><img src="https://user-images.githubusercontent.com/23721611/218310394-d9494249-387d-4fb3-9fa0-aff8a570b9b6.png" alt="image"></p>
<p>先不管死掉的<code>IE</code>(如果你们的产品要兼容就当我没说)，其他浏览器的兼容性还是可以的，<code>Chrome 57</code>的发布时间是<code>2017-03-09</code>，<code>Edge 16</code>则是<code>2017-10-17</code>，其他几家也都是<code>2017</code>年跟进的，算起来也是很多年前的事情了，是吗？</p>
<p>别废话，都2023年了，还不升级六年前版本浏览器的用户不配当我们的产品的用户！</p>
<p>那我们是不是可以直接一把梭用<code>gap</code>？</p>
<p>注意看这个数据：<code>China: 53.83%</code>! 这就意味着，中国所有浏览器中，仅仅有<code>53.83%</code>使用份额的浏览器得到完全支持或部分支持。</p>
<p>看到这个数据，我立马想起自己在项目里大量使用了<code>gap</code>来创建元素之间的间隙样式，而我们的产品又面向了比较多的老用户。。。</p>
<p>于是：</p>
<p><img src="https://user-images.githubusercontent.com/23721611/218310407-5401b98c-9eab-4bc0-af1d-29eff1d09268.png" alt="image"></p>
<h3>最后</h3>
<p>当你需要考虑产品用户设备兼容性时，你最好不要用。</p>
<p>当你需要考虑所有用户设备兼容性时却<code>能接受</code>老大突然在<code>下班时间</code>让你修复某个用户因这个属性导致的页面布局异常时，你可以爱怎么用就怎么用。</p>
<p>当你在开发自己的项目，亦或是学习一些新知识时，这个属性还是非常方便的，这时候我就可以很硬气地说不升级浏览器版本的用户不配当我的用户。</p>
<p>另外，相对于<code>margin</code>属性来实现相同的效果来说，其大大减少了代码量，并且对国际化的支持也非常棒。</p>
<blockquote>
<p>题外话，如果你需要考虑国际化，你可能会因为<code>margin</code>问题导致某些国家从右到左的阅读方向导致的布局效果而加班😂。</p>
</blockquote>
<p>这个属性就水到这里，其他的下次再说。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[特殊的周刊，失败的经验]]></title>
            <link>https://i5lin.top/blog/post/1957473494</link>
            <guid isPermaLink="false">1957473494</guid>
            <pubDate>Mon, 23 Oct 2023 15:44:45 GMT</pubDate>
            <description><![CDATA[今天分享的是一份我的特殊周刊。]]></description>
            <content:encoded><![CDATA[<p>今天分享的是一份我的特殊周刊。</p>
<img width="893" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/ce509ac2-369f-4b6c-bee6-a57cb9502921"><p>学生时代见到台上某些人在分享自己的一些观念、经历、成果等等，无一例外都是些能“激励人心”的东西。谢谢分享，那些激励至少能在我身上发挥三分钟到三天时间的效果。</p>
<p>我一直觉得让自己保持自律是一件反人类的事情，对我来说，给自律做事的人献上掌声简直是最自然不过的事情，而那些能保持自律成自然的人，我更是由衷地佩服（无论对方在自律做什么事，无论什么事）。</p>
<p>或许像笔者这样的普通人，有时候需要的并不是更多“激励人心”的东西，而是反过来。</p>
<p>今天，我来分享一些最近自己没有坚持做完而半途而废的项目（小玩具）或学习经历。</p>
<blockquote>
<p>此刻在听《Speechless》Dan + Shay (网易云音乐)</p>
</blockquote>
<h2>Rust CLI</h2>
<p>Rust 是新世代的系统级编程语言，我或许可以学习一下，未来可以给自己写一些系统小应用。</p>
<p>某个时间点，我看到<a href="https://github.com/sharkdp/bat/blob/master/doc/logo-header.svg"><a href="https://github.com/sharkdp/bat/blob/master/doc/logo-header.svg">sharkdp/bat: A cat(1) clone with wings.</a></a>这样的 CLI 工具，它完全由<code>Rust</code>开发。相对于老旧传统的<code>cat</code>命令来说，简直华丽无比。或许我可以学习<code>Rust</code>，然后把自己那么喜欢的<code>Terminal</code>小工具都给重新写一遍！</p>
<p>...</p>
<p>不知道被什么耽搁了，终止于<code>Rust</code>官方教程<code>Control flow</code>章节。</p>
<p>到底被什么耽搁了，我不记得了。</p>
<p>Holy  💩</p>
<h2>岚软</h2>
<p>笔者喜欢接触新奇有趣的软件，因此也算是接触到了很多很棒的软件，如果我将之分门别类，做成一个开放的 Web 门户应用如何？</p>
<p>这将会帮助到笔者自己收集和整理自己的软件&quot;后花园&quot;，还可以让需要用的人自取。能分享好用、开源的应用给其他人也不错。</p>
<p>说干就干，做了一段时间之后我发现“说干就干”简直毫无意义。这个项目应该更有价值，它至少应该有完整的<code>UI</code>设计图！</p>
<p>Holy  💩 </p>
<p>我得去学学<code>Figma</code>，设计不求人！谢谢前同事老范推荐的课程，至少我没有对不起你的信任，课程大纲我看了，很赞。</p>
<p>在厦门的时候我打算花点时间学<code>Figma</code>，然后就没有然后了。</p>
<p>我好像被我的懒惰耽搁了，当然还有加班。</p>
<p>Holy  💩 不管了，界面丑就丑吧，又不是不能用。</p>
<p>于是乎我继续用<code>Next.js</code>去写后端，看上了<code>ORM</code>和<code>MongoDB</code>，吭哧吭哧写了一段时间又觉得<code>Next.js</code>的后端能力简直辣鸡！</p>
<p>为什么不趁此机会去学学<code>Ruby</code>呢！好像国外<code>Ruby</code>的工作机会还挺多的。</p>
<p>我现在的<code>Ruby</code>笔记还在，但我什么也不记得了。</p>
<p>Holy  💩</p>
<h2>Twitter 插件</h2>
<p>当初<code>Twitter</code>还叫<code>Twitter</code>，我被某些神秘推文疯狂轰炸，仿佛中文圈都是那股势力在活跃。除了我之外 ，还有很多人也有相同的困扰（或许是少数，毕竟说不定大家都喜欢）。</p>
<p>于是乎我决定写一个<code>Chrome</code>插件去自动屏蔽那些账户，其实还算顺利。我将主站的请求抽丝剥茧得到了伪装请求的所有数据，此时我需要一个后台来提供订阅服务。简而言之，大家可以订阅自己喜欢的组织，信任组织里的朋友能在神秘内容下遵守自己的原则，大家共同出力！</p>
<p>最终依靠“人工”来实现智能，批量屏蔽大家都不喜欢的东西。（我甚至还反向支持了大家订阅一个大家都喜欢的内容组织，来共享自己喜欢的账号 ）。</p>
<p>但是，到了部署和测试的时候，插件还需要时间和精力来实现一些功能，部署也需要服务器等等。</p>
<p>Holy  💩 你知道我要说什么，我也不知道为什么会半途而废。</p>
<h2>NBA 快讯</h2>
<p><code>PWA</code>很棒，<code>NBA</code>很棒。</p>
<p>某讯体育简直是<code>AI</code>写手和广告天堂，毫无可信度的自媒体和无底线的管理机制。。。</p>
<p>为什么不自己每天花点时间去收集自己喜欢的信息，用<code>PWA</code>来承载，做点什么出来呢！</p>
<p>Holy  💩 从没开始</p>
<h2>Vim 坚持</h2>
<p><img src="https://github.com/ecosse3/nvim/raw/master/.screenshots/6-alpha.png" alt="Dashboard"></p>
<p>vim 是程序员的浪漫，vim 是编辑器之神。</p>
<p>Holy  💩 坚持多次都中途转向<code>VSCode</code>，我有罪。</p>
<h2>Canvas 学习</h2>
<p>工作上需要使用<code>Canvas</code>去实现一些动画，因此我打算系统性学习一下<code>Canvas</code>的知识。</p>
<p>Holy  💩 996 让我头皮发麻，连周刊都停下来了。</p>
<h2>翻译 PlasmoDoc</h2>
<p>群友打赏支持我翻译和分享<code>PlasmoDoc</code>的知识，没想到我收红包还不办事。</p>
<p>Holy  💩 在路上，我记得这件事。</p>
<h2>Chrome Extension 系列</h2>
<ul>
<li>IPassword 密码管理器（密码东西其实没有那么简单，我被卡住了咽喉</li>
<li>Flashing Download 代替 Chrome 垃圾下载功能，但是我却想加上后端支持，让用户能直接搜索软件和让下载某些东西变得更方便（我说的是油管那些），但后端又成了拦路虎，再后来 Chrome 新版本支持了一个不错的下载管理器 Holy  💩</li>
<li>TodoList 万恶之源，写是时候觉得应该把之前写得很多的数据存储的工具函数整理成库，但是被什么耽搁了？？？于是乎。。。</li>
<li>ADBlocker 分析网络请求实现广告拦截 Holy  💩 </li>
<li>Chrome 插件快速管理器（没时间</li>
<li>...</li>
</ul>
<h2>Blog 重构</h2>
<p>基于<code>Github</code>的博客始终少了些什么，到底少了什么？不够稳定，速度也很慢，访问还需要魔法。</p>
<p>Holy  💩</p>
<p>少了内容，少了应对备案的决心。</p>
<h2>最后</h2>
<p>哎，叹息。</p>
<p>好了，今天的分享就先到这里，大家下周见，绝对不🕊</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[JavaScript Class 浅解]]></title>
            <link>https://i5lin.top/blog/post/2004818507</link>
            <guid isPermaLink="false">2004818507</guid>
            <pubDate>Tue, 21 Nov 2023 17:39:12 GMT</pubDate>
            <description><![CDATA[在 2015 年的时候，class 才正式随着 ES6 的发布加入到了 JavaScript 家族中来。]]></description>
            <content:encoded><![CDATA[<p>在 2015 年的时候，<code>class</code>才正式随着<code>ES6</code>的发布加入到了<code>JavaScript</code>家族中来。</p>
<p>在写这个内容的时候，笔者对<code>class</code>的使用经验寥寥无几。因此，本文既是分享，也是学习。</p>
<p><img src="https://images.unsplash.com/photo-1534787238916-9ba6764efd4f?q=80&w=2531&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt=""></p>
<h2>起源</h2>
<blockquote>
<p>面向对象编程（Object-oriented programming，简称 OOP）是一种软件编程范式，它是一种基于对象和类的编程方法。</p>
</blockquote>
<p><code>类(class)</code>是面向对象编程中的重要概念，简单来说，开发者用<code>class</code>来创建对象的结构和行为的定义。</p>
<p>类的一些重要概念和特点包括：</p>
<ol>
<li><strong>封装（Encapsulation）</strong>：类允许将相关的数据和方法封装在一起，以实现对数据的隐藏和保护。通过访问修饰符（如私有、公共、受保护等），可以控制对类的成员的访问权限。</li>
<li><strong>继承（Inheritance）</strong>：类可以通过继承机制派生出子类，子类可以继承和扩展父类的属性和方法。继承支持代码的重用和创建层次结构。</li>
<li><strong>多态（Polymorphism）</strong>：多态允许对象在不同的上下文中表现出不同的行为。通过方法的重写和重载，可以在父类引用的基础上调用不同子类的方法。</li>
</ol>
<p>在<code>ES6</code>规范发布之前，<code>JavaScript</code>是没有<code>class</code>这个正式的概念的。但是语言规范的制定者和社区开发者们都需要<code>OOP</code>这样的编程范式来应对自己的需求。</p>
<p>在类出现在规范上之前，开发者创建对象的方式有很多种，并且都有自己各自的问题。我们会花一点时间逐一讨论这些方案。</p>
<h3>工厂函数</h3>
<p>首先是<code>工厂函数</code>创建对象：</p>
<pre><code class="language-js">function createPerson(name, age) {
  let o = new Object()
  o.name = name
  o.age = age
  o.sayName = function() {
    console.log(this.name)
  }
  return o;
}
const p1 = createPerson(&#39;Lily&#39;, 24);
const p2 = createPerson(&#39;Juice&#39;, 20)
</code></pre>
<p>利用工厂函数创建对象的不足在于<code>无法判断新创建的对象的类型</code>。</p>
<h3>构造函数</h3>
<p>在<code>JavaScript</code>中存在<code>构造函数</code>的概念，用于创建特定类型的对象。</p>
<p>一个典型的构造函数即一个以大写字母开头命名的函数，函数体内使用<code>this</code>来设置返回对象的属性，不需要显式创建对象和指定<code>return</code>对象，这也是跟工厂函数的差别之一：</p>
<pre><code class="language-js">function Person(name, age) {
  this.name = name
  this.age = age
  this.getAge = function() {
    console.log(this.age)
    return this.age
  }
}

// 通过构造函数创建对象
const p1 = new Person(&#39;July&#39;, 18);
const p2 = new Person(&#39;Rose&#39;, 18);
</code></pre>
<p>在很多初级 JavaScript 开发面试的时候有一道经典面试题，面试官可能会问使用<code>new</code>操作费创建对象会执行那些操作。</p>
<p>这个问题的答案如下：</p>
<ul>
<li>在内存中创建一个新对象</li>
<li>新对象的内部属性<code>[[prototype]]</code>被赋值给了构造函数的<code>prototype</code>属性</li>
<li>构造函数的<code>this</code>指向了新创建的对象</li>
<li>执行构造函数体内的逻辑（通常是给新创建的对象添加属性和方法）</li>
<li>如果构造函数返回非空对象，则返回此对象。否则，返回新创建的对象。（通常我们不会指定<code>return</code>返回值，默认返回的是新对象）</li>
</ul>
<p>如上所示的示例，<code>p1</code>存在以下属性连接：</p>
<pre><code class="language-js">p1.constructor === Person
</code></pre>
<p>显然，我们可以通过<code>constructor</code>来确定对象的类型是<code>Person</code>。</p>
<p>但是，大多数情况下你可能会看到开发者使用<code>instanceof</code>来确定对象的类型：</p>
<pre><code class="language-js">console.log(p1 instanceof Person); // true
</code></pre>
<p>构造函数创建对象解决了工厂函数无法确定对象类型的问题，但是还是有缺点：<code>每次创建的对象都会添加构造函数里定义的方法</code>。上述例子中创建的<code>p1</code>和<code>p2</code>对象都具有名为<code>getAge</code>的函数方法，二者虽然在定义的代码上看是相同的，实际却不等。</p>
<p>为了弥补构造函数这个瑕疵，我们可以利用<code>JavaScript</code>的原型模式来解决问题。</p>
<h3>原型模式</h3>
<p>我们知道，构造函数也是函数。每一个函数都有一个<code>prototype</code>属性，这个属性是一个对象，此对象上的属性和方法可以被所有由此构造函数创建的对象实例所共享。</p>
<blockquote>
<p>更多原型模式的知识推荐阅读《JavaScript 高级程序设计（第四版）》</p>
</blockquote>
<pre><code class="language-js">function Person() {}
Person.prototype.name = &#39;Rose&#39;
Person.prototype.getName = function() {
  console.log(this.name)
}
let p1 = new Person()
p1.getName(); // Rose
let p2 = new Person()
p2.getName(); // Rose
</code></pre>
<p>原型的关系如图所示：</p>
<p><img src="https://blog.ismeoh.com/usr/uploads/2023/10/4119007437.png" alt="img"></p>
<p>因此，<code>p1</code>和<code>p2</code>执行<code>getName</code>方法输出的是构造函数的<code>prototype（原型对象）</code>里共享的属性<code>name</code>。</p>
<p>原型是有层级的，构造函数的原型对象也是对象，因此也有原型。上述例子中，<code>Person</code>函数的原型对象的原型就是顶层对象的原型<code>Object.prototype</code>，因此<code>p1</code>对象可以调用<code>p1.toString()</code>方法。</p>
<p>这个方法源于<code>Object.prototype</code>原型对象，当我们给<code>p1</code>添加上<code>toString()</code>方法之后：</p>
<pre><code class="language-js">p1.toString = function() {
  console.log(&#39;p1&#39;)
}
</code></pre>
<p>它在调用时会先从自身搜索这个方法，不存在则往原型对象上搜索共享方法，直到<code>Object.prototype</code>对象为止。</p>
<p>上述例子我们主动添加了<code>toString()</code>方法，则后续调用时会执行此方法输入<code>p1</code>。</p>
<h3>原型继承</h3>
<p>很多面向对象语言中的继承包含接口继承和实现继承，而<code>JavaScript</code>中的函数没有类型签名，因此接口继承无从谈起。<code>JavaScript</code>中的继承即实现继承，主要由原型链的机制来实现。</p>
<blockquote>
<p>代码示例来源于《JavaScript 高级程序设计》</p>
</blockquote>
<p>我们来看如下的代码：</p>
<pre><code class="language-js">function SuperType() {
  // 设置 this 的属性
  this.property = true;
}

SuperType.prototype.getSuperValue = function() {
  return this.property;
}

function SubType() {
  this.subProperty = false;
}

// 构造函数不使用默认原型，而是设置为 SuperType 的实例对象
// 补充：默认原型就是 Object.prototype
SubType.prototype = new SuperType();

SubType.prototype.getSubTypeValue = function() {
  return this.subProperty;
}

// 创建实例
let instance = new SubType();
console.log(instance.getSuperValue()); // 输出 true
</code></pre>
<p>如图所示：</p>
<img width="943" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/fd43ce22-79e4-441d-bd3d-b7eefc88ec01"><p><code>SubType</code>构造函数创建的实例对象继承了<code>SuperType</code>的原型上的方法，因此可以调用<code>getSuperValue()</code>，并且在原型链上搜索<code>property</code>的时候，先从自身搜索、再从原型搜索。而原型恰好是<code>SuperType</code>的实例，因此原型上存在<code>property</code>属性。</p>
<p><code>JavaScript</code>的继承由此展开。</p>
<h3>原型继承的缺陷</h3>
<p>首先原型继承之后，继承的属性在不同的实例中是共享的。其次，子类型实例化的时候无法给父类型的构造函数传参。再次，子构造函数可以重新通过字面量的方式设置原型对象，从而会让之前的继承断裂。</p>
<h4>思索</h4>
<p>为了在创建实例的时候给父类型的构造函数传参，有人提出了一个“没什么用的”方案：</p>
<pre><code class="language-js">function SuperType(name) {
  this.name = name;
  this.colors = [&#39;red&#39;];
};

function SubType(name) {
  SuperType.call(this, name)
}

let instance = new SubType(&#39;Rose&#39;)
instance.colors.push(&#39;aliceblue&#39;)
console.log(instance.colors); // red, aliceblue
console.log(instance.name); // Rose

let instance2 = new SubType(&#39;Jay&#39;)
console.log(instance.colors); // red
console.log(instance.name); // Jay
</code></pre>
<p>不错吧，利用<code>call</code>函数可以给父类型构造函数传参了，实例也能通过父类型的属性初始化自己的属性，并且不会影响其他实例。</p>
<p>但是，这种被称为<code>盗用构造函数</code>的模式实例化的对象却无法使用父类型构造函数原型上的方法，因此很多人觉得这玩意你看了笑笑就好。</p>
<h3>原型式继承</h3>
<p>来看看这个函数：</p>
<pre><code class="language-js">function createObject(obj) {
  function F(){}
  F.prototype = obj
  return new F();
}
</code></pre>
<p>当我们传入一个对象的时候，返回值就是一个新的对象，这个新对象是<code>createObject</code>函数内临时构造函数<code>F</code>的实例，并且由于我们指定了这个实例的原型对象为传入的对象，那么最终返回的对象实例的原型就是传入的对象。</p>
<p>这在某种程度上也是一种继承方式，并且这种继承方式不需要我们定义构造函数。需要注意的是，这种方式所创建的对象会共享传入作为原型的对象，当你真的需要基于一个对象去快速创建新对象，并且需要共享这个原型对象的属性的话，这种方式确实很适合你。</p>
<h3>组合继承</h3>
<p>为了解决盗用构造函数模式的缺点，有人提出了<code>组合继承</code>的实现方案：</p>
<blockquote>
<p>组合什么？当然是组合原型继承和盗用构造函数模式继承的优点！</p>
</blockquote>
<pre><code class="language-js">function SuperType(name) {
  this.name = name
  this.color = [&#39;red&#39;]
}

SuperType.prototype.sayName = function() {
  console.log(this.name)
}

function SubType(name, age) {
  // 继承父类型 SuperType 的属性
  SuperType.call(this, name) // 第二次调用
  this.age = age
}
// 继承父类型原型上的方法
SubType.prototype = new SuperType() // 第一次调用

SubType.prototype.sayAge = function() {
  console.log(this.age)
}

let instance1 = new SubType(&#39;Rose&#39;, 18)
instance1.color.push(&#39;blue&#39;)
console.log(instance1.color) // red, blue
instance1.sayName() // Rose
instance1.sayAge() // 18

let instance2 = new SubType(&#39;Jay&#39;, 99)
console.log(instance1.color) // red 颜色是构造函数内创建的，独立的（当然实例的原型链上也有color，这个是共享的）
instance1.sayName() // Jay
instance1.sayAge() // 99
</code></pre>
<p>组合继承弥补了原型继承和盗用构造函数继承的缺点，因此被社区广泛接受和推广。</p>
<p><strong>但是</strong>，这种方式依然存在不足之处：<code>SuprtType()</code>被调用了两次。</p>
<p>后来，开发者们考虑到可以利用原型式继承去代替显式地创建<code>SuperType</code>的实例对象设置原型的方案。</p>
<pre><code class="language-js">function SuperType(name) {
  this.name = name;
  this.color = [&#39;red&#39;]
}

SuperType.prototype.sayName = function() {
  console.log(this.name)
}

function SubType(name, age) {
  SuperType.call(this, name)
  this.age = age
}

function inheritProperty(subType, superType) {
  let prototype = createObject(superType.prototype); 设置父类型的原型，创建一个对象作为原型对象
  prototype.constructor = subType; // 修复了原型上 constructor 的指向
  subType.prototype = prototype; // 设置子类型原型对象
}

inheritProperty(SubType, SuperType); // 通过原型式继承设置原型，而非显式通过 new SuperType 创建原型

// 增强原型
SubType.prototype.sayAge = function() {
  console.log(this.age)
}
</code></pre>
<p>如此一来即可只调用一次<code>SuperType</code>原型函数，并且最终<code>SubType</code>对象实例的<code>instanceof</code>依然有效。这</p>
<p>这就是<code>ES6</code>之前的继承最佳模式，也是那个时候开发者编写<code>OOP</code>范式代码的基础。</p>
<h3>ES6 Class</h3>
<p>在<code>ES6</code>发布之后，<code>Class</code>正式为类和面向对象编程提供了语言规范级别的定义，开发者们可以不必再处理冗长的继承逻辑代码，直接使用官方提供的新语法糖<code>Class</code>即可。</p>
<p>举个例子，首先是简单的类：</p>
<pre><code class="language-js">// 不可实例化的抽象基类，通过 new.target 控制使其无法通过 new 实例化，从而得到一个仅用于继承的基类
class Animal {
  // 使用 new 调用操作符创建类的实例的时候会调用这个构造函数（但这个构造函数是可选的，不包含构造函数的类定义也是合法的）
  constructor(name) {
    // 实例化的时候检测到 target 为 Animal 即抛出异常
    if (new.target === Animal) {
      throw new Error(&quot;Animal class cannot be instantiated.&quot;);
    }
    this.name = name;
  }

  // 访问器
  get getName() {
    return this.name;
  }

  // 实例方法
  makeSound() {
    console.log(&quot;Some generic sound&quot;);
  }

  // 静态方法
  static staticMethod() {
    console.log(&quot;Animal static method called&quot;);
  }

  // 生成器
  *generateSounds() {
    yield &quot;Sound 1&quot;;
    yield &quot;Sound 2&quot;;
  }

  // 迭代器
  [Symbol.iterator]() {
    let index = 0;
    const sounds = [&quot;Sound 1&quot;, &quot;Sound 2&quot;];
    return {
      next: () =&gt; ({
        value: sounds[index++],
        done: index &gt; sounds.length,
      }),
    };
  }
}
</code></pre>
<blockquote>
<p>Class 是特殊的函数：<code>typeof Animal</code>输出<code>function</code>，因此你想要将之定义在数组中、像其他对象或函数引用一样作为参数传递亦或是立即实例化、查看原型都是可以的，即使我们平时几乎不会这么做。</p>
</blockquote>
<p>为了实现多继承，使用<code>混入</code>机制：</p>
<pre><code class="language-js">// 混入实现多继承
const SoundMixin = {
  // 实例方法
  makeSound() {
    console.log(&quot;Mixin sound&quot;);
    // 使用 super 调用基类方法
    super.makeSound();
  }

  // 新的实例方法
  additionalMethod() {
    console.log(&quot;Mixin additional method&quot;);
  },

  // 新的静态方法
  staticMixinMethod() {
    console.log(&quot;Mixin static method&quot;);
  }
};
</code></pre>
<p>继续，添加一个子类，并且子类继承父类：</p>
<pre><code class="language-js">// 使用混入创建一个新类
class Dog extends Animal {
  constructor(name, breed) {
    // 派生类必须选择：1.调用 super 函数 2.显式 return 一个对象
    super(name);
    this.breed = breed;
  }

  // 重写父类方法
  makeSound() {
    console.log(&quot;Woof! Woof!&quot;);
    // 使用 super 调用基类方法
    super.makeSound();
  }

  // 新的实例方法
  fetch() {
    console.log(&quot;Fetching the ball&quot;);
  }
}
</code></pre>
<p>接着使用混入机制将混入的对象设置为类的原型，再来创建类的实例：</p>
<blockquote>
<p>super 只能在派生类（继承了其他类的类）构造函数和静态方法中使用，并且在 constructor 函数内先于<code>this</code>使用之前调用<code>super()</code>（等同于调用父类的构造函数）</p>
</blockquote>
<pre><code class="language-js">// 将混入的方法添加到子类
Object.assign(Dog.prototype, SoundMixin);

// 创建类的实例
const myDog = new Dog(&quot;Buddy&quot;, &quot;Golden Retriever&quot;);

// 调用实例方法
myDog.makeSound();                // 输出: Woof! Woof! \n Some generic sound
myDog.additionalMethod();         // 输出: Mixin additional method

// 调用静态方法
Dog.staticMethod();               // 输出: Animal static method called
Dog.staticMixinMethod();          // 输出: Mixin static method

// 使用访问器
console.log(myDog.getName);       // 输出: Buddy

// 使用生成器
for (const sound of myDog.generateSounds()) {
  console.log(sound);
}

// 使用迭代器
for (const sound of myDog) {
  console.log(sound);
}
</code></pre>
<blockquote>
<p>上述的代码示例涉及了类许多的知识点，但如果你想更加详细地了解类，欢迎阅读《JavaScript 高级程序设计》。</p>
</blockquote>
<h3>优化类相关的代码</h3>
<h4>方法链</h4>
<p>在创建类的实例方法定义时，推荐使用方法链模式：</p>
<blockquote>
<p>当链变长的时候，连续调用的代码会更加简洁和清晰，你可以在像<code>lodash</code>、<code>rxjs</code>这样的库上看到这样的模式。</p>
</blockquote>
<pre><code class="language-js">// 不推荐的写法
class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
  }

  setModel(model) {
    this.model = model;
  }

  setColor(color) {
    this.color = color;
  }

  save() {
    console.log(this.make, this.model, this.color);
  }
}

const car = new Car(&quot;Ford&quot;, &quot;F-150&quot;, &quot;red&quot;);
car.setColor(&quot;pink&quot;);
car.save();
</code></pre>
<p>推荐优化：</p>
<pre><code class="language-js">class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
    // NOTE: Returning this for chaining
    return this;
  }

  setModel(model) {
    this.model = model;
    // NOTE: Returning this for chaining
    return this;
  }

  setColor(color) {
    this.color = color;
    // NOTE: Returning this for chaining
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
    // NOTE: Returning this for chaining
    return this;
  }
}

const car = new Car(&quot;Ford&quot;, &quot;F-150&quot;, &quot;red&quot;);
car.setColor(&quot;pink&quot;)
  .save();
</code></pre>
<h4>组合和继承的选择</h4>
<p>组合和继承各自有各自的优点，选择哪种模式取决于具体的场景，但是我们也有可以参考的点：</p>
<ul>
<li>继承侧重于二者具有<code>is-a(是一个....)</code>的关系，而非<code>has-a(有一个...)</code></li>
<li>当基类代码具有高度的可重用性时，继承会更好</li>
<li>当你希望通过修改一次基类即可让全局都能响应变更</li>
</ul>
<p>上述三点似乎有些抽象，让我们来看一个代码示例：</p>
<pre><code class="language-js">class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  // ...
}

// Bad because Employees &quot;have&quot; tax data. EmployeeTaxData is not a type of Employee
// 使用继承并非良策：雇员有数据，而数据并不是一种雇员
class EmployeeTaxData extends Employee {
  constructor(ssn, salary) {
    super();
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}
</code></pre>
<p>优化代码：</p>
<pre><code class="language-js">class EmployeeTaxData {
  constructor(ssn, salary) {
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  setTaxData(ssn, salary) {
    // 直接在设置数据的时候，组合不同对象的实例
    this.taxData = new EmployeeTaxData(ssn, salary);
  }
  // ...
}
</code></pre>
<h4>单一职责</h4>
<p>一个类应该只有一个引起它变化的原因，如果一个类负责太多不同的事情，那么当其中某个功能发生变化的时候，就可能导致整个类需要修改，这使得代码更加脆弱、难以维护和理解。</p>
<p>将类设计得简洁、保持单一性，有利于减少修改类的逻辑的次数，这样能够降低引入错误的风险，对于阅读代码的人来说也更有好处。</p>
<p>举个反面例子：</p>
<pre><code class="language-js">class UserSettings {
  constructor(user) {
    this.user = user;
  }

  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }

  verifyCredentials() {
    // ...
  }
}
</code></pre>
<p>优化一下，将验证内容分离出去：</p>
<pre><code class="language-js">class UserAuth {
  constructor(user) {
    this.user = user;
  }

  verifyCredentials() {
    // ...
  }
}

class UserSettings {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(user);
  }

  changeSettings(settings) {
    if (this.auth.verifyCredentials()) {
      // ...
    }
  }
}
</code></pre>
<p>当我们需要修改验证逻辑的时候，就不会修改任何<code>UserSettings</code>的内容了，这在代码变得复杂之后，效果尤为明显。</p>
<h4>打开和关闭原则</h4>
<p>这是面向对象设计中的&quot;开闭原则&quot;（Open/Closed Principle）的表述。Bertrand Meyer指出，软件实体（如类、模块、函数等）应该对扩展开放，但对修改关闭。理解这一原则主要包括以下几个方面：</p>
<ol>
<li><p><strong>对扩展开放（Open for Extension）：</strong> 意味着系统的设计应该允许在不修改现有代码的情况下引入新功能。新的功能可以通过添加新的代码、新的类、或者新的模块来实现，而不需要修改已经存在的代码。</p>
</li>
<li><p><strong>对修改关闭（Closed for Modification）：</strong> 意味着一旦一个软件实体（如类）被创建并投入使用，就不应该再对其进行修改。这样可以确保已有的代码在添加新功能时不会受到影响，从而提高系统的稳定性和可维护性。</p>
</li>
</ol>
<p>具体来说，这一原则的目的是为了避免在引入新功能时对已有的代码进行修改，因为这样的修改可能引入错误，破坏已有的功能，或者导致不稳定性。相反，通过允许扩展现有系统，可以保持系统的稳定性，并且更容易维护。</p>
<p>一个常见的实践是通过使用抽象接口和多态性来实现开闭原则。通过定义接口或抽象类，系统的不同部分可以通过实现这些接口或继承这些抽象类来扩展功能，而无需修改已有的代码。这样的设计使得系统更具弹性，更容易适应变化。</p>
<p>举个例子：</p>
<pre><code class="language-js">class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = &quot;ajaxAdapter&quot;;
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = &quot;nodeAdapter&quot;;
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    if (this.adapter.name === &quot;ajaxAdapter&quot;) {
      return makeAjaxCall(url).then(response =&gt; {
        // transform response and return
      });
    } else if (this.adapter.name === &quot;nodeAdapter&quot;) {
      return makeHttpCall(url).then(response =&gt; {
        // transform response and return
      });
    }
  }
}

function makeAjaxCall(url) {
  // request and return promise
}

function makeHttpCall(url) {
  // request and return promise
}
</code></pre>
<p>上面的例子里，让开发者需要添加新的网络适配器的时候，就必须修改基础的<code>HttpRequester</code>类的逻辑，让我们来优化一下：</p>
<pre><code class="language-js">class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = &quot;ajaxAdapter&quot;;
  }

  request(url) {
    // request and return promise
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = &quot;nodeAdapter&quot;;
  }

  request(url) {
    // request and return promise
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then(response =&gt; {
      // transform response and return
    });
  }
}
</code></pre>
<p>在适配器中提供相同的<code>request</code>方法，从而提高了扩展性，又不需要修改基础类的逻辑。</p>
<h4>里式替换</h4>
<p>Liskov Substitution Principle（LSP）是面向对象设计中 SOLID 原则的一部分，强调了子类型（子类）应该能够替代其基类型（基类）而不引起问题。LSP的存在有几个重要原因：</p>
<ol>
<li><p><strong>可替代性（Substitutability）：</strong> LSP确保了在程序中的任何需要基类型的地方都可以安全地使用子类型，而不需要了解具体的子类型。这提高了代码的可复用性和灵活性。</p>
</li>
<li><p><strong>一致性和可预测性：</strong> 当子类型可以替代基类型时，程序的行为更为一致和可预测。如果在使用基类型的地方使用子类型，程序的行为应该与基类型的预期行为一致，而无需考虑具体的子类型。</p>
</li>
<li><p><strong>简化设计和理解：</strong> LSP鼓励设计者创建简单一致的类层次结构。通过确保子类型可以替代基类型，我们避免了在使用这些类型时需要进行过多的条件判断或特例处理。这样的设计更易于理解和维护。</p>
</li>
<li><p><strong>降低耦合度：</strong> 当子类型可以替代基类型时，系统的各个部分之间的耦合度降低。这意味着可以更轻松地对系统进行修改和扩展，而不会引入意外的行为或破坏现有的功能。</p>
</li>
<li><p><strong>便于测试：</strong> LSP有助于创建更容易测试的代码。如果子类型可以替代基类型，我们可以使用基类型的实例或模拟来进行测试，而不需要在测试时专门处理每个具体的子类型。</p>
</li>
</ol>
<p>总体而言，Liskov Substitution Principle在面向对象设计中的价值在于创建更为一致、可扩展和可维护的代码，提高代码的质量和可读性，同时减少引入错误的风险。</p>
<p>编写继承的代码时，子类如需新增功能，则尽可能避免新功能方法名与父类方法重名，以免让父类方法调用时出错。</p>
<p>准确地说，继承复用的关键在于不影响原功能，子类应该能够替代基类而不引起错误！</p>
<p>举个例子：</p>
<pre><code class="language-js">class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  // 正方形特有的行为
  setWidth(width) {
    this.width = width;
    this.height = width;
  }

  setHeight(height) {
    this.width = height;
    this.height = height;
  }
}
</code></pre>
<p>在这个例子中，<code>Square</code> 类继承自 <code>Rectangle</code> 类，因为正方形可以被视为一种特殊情况的长方形。然而，由于正方形要求宽度和高度始终相等，<code>Square</code> 类覆盖了 <code>setWidth</code> 和 <code>setHeight</code> 方法以保持这一特性。</p>
<p>现在，让我们看一个使用这些类的情况：</p>
<pre><code class="language-js">function calculateArea(rectangle) {
  rectangle.setWidth(5);
  rectangle.setHeight(10);
  return rectangle.getArea();
}

const rectangle = new Rectangle(5, 10);
console.log(calculateArea(rectangle)); // 输出 50

const square = new Square(5);
console.log(calculateArea(square)); // 输出 25，而不是预期的 50
</code></pre>
<p>在这个例子中，<code>calculateArea</code> 函数期望一个长方形，但当我们将一个正方形传递给它时，由于正方形覆盖了 <code>setWidth</code> 和 <code>setHeight</code> 方法，导致最终计算的面积不符合预期。这就违反了 Liskov Substitution Principle。</p>
<p>推荐的写法如下：</p>
<pre><code class="language-js">class Shape {
  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Shape {
  constructor(length) {
    super();
    this.length = length;
  }

  getArea() {
    return this.length * this.length;
  }
}

function renderLargeShapes(shapes) {
  shapes.forEach(shape =&gt; {
    const area = shape.getArea();
    shape.render(area);
  });
}

const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);
</code></pre>
<h4>依赖反转原则</h4>
<p>依赖反转原则（Dependency Inversion Principle，DIP）是面向对象设计原则之一，提倡高层模块不应该依赖于底层模块，二者都应该依赖于抽象；而抽象不应该依赖于细节，细节应该依赖于抽象。这个原则有助于实现松耦合，提高代码的灵活性和可维护性。</p>
<p>先看一个正面例子：</p>
<pre><code class="language-js">// 定义抽象的消息发送器接口
class MessageSender {
  send(message) {}
}

// 实现具体的邮件发送器
class EmailSender extends MessageSender {
  send(message) {
    console.log(`Sending email: ${message}`);
  }
}

// 实现具体的短信发送器
class SmsSender extends MessageSender {
  send(message) {
    console.log(`Sending SMS: ${message}`);
  }
}

// 高层模块（业务逻辑）依赖于抽象
class NotificationService {
  constructor(messageSender) {
    this.messageSender = messageSender;
  }

  sendNotification(message) {
    this.messageSender.send(message);
  }
}

// 使用示例
const emailSender = new EmailSender();
const smsSender = new SmsSender();

const notificationServiceWithEmail = new NotificationService(emailSender);
notificationServiceWithEmail.sendNotification(&quot;Hello via email!&quot;);

const notificationServiceWithSms = new NotificationService(smsSender);
notificationServiceWithSms.sendNotification(&quot;Hello via SMS!&quot;);
</code></pre>
<p>再看一个反面例子：</p>
<pre><code class="language-js">// 高层模块直接依赖于底层模块的具体实现，违反了依赖反转原则
class NotificationServiceViolatingDIP {
  constructor() {
    this.emailSender = new EmailSender();
  }

  sendNotification(message) {
    this.emailSender.send(message);
  }
}

// 使用示例
const notificationServiceViolatingDIP = new NotificationServiceViolatingDIP();
notificationServiceViolatingDIP.sendNotification(&quot;Hello via email!&quot;);
</code></pre>
<p>如此一来，高层模块和邮件发送器耦合性很紧密，如果要扩展发送器类型则需要修改高层模块代码，相对正面例子来说灵活性和可维护性都有所降低。</p>
<h2>最后</h2>
<p>关于<code>JavaScript</code>类的分享就到这里了，下次我将分享一些关于测试的内容。</p>
<p>Bye.</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[骑行使我快乐]]></title>
            <link>https://i5lin.top/blog/post/1977892714</link>
            <guid isPermaLink="false">1977892714</guid>
            <pubDate>Sun, 05 Nov 2023 17:52:04 GMT</pubDate>
            <description><![CDATA[
---
description: 大家好~今天继续给大家带来我的新周刊内容。
cover: https://github.com/Developer27149/blog/assets/23721611/3c5f8d90-980e-45c3-8e0e-f3921fed1fd8
---

hello 大家好~今天继续给大家带来我的新周刊内容。

![image](https://gi]]></description>
            <content:encoded><![CDATA[<hr>
<h2>description: 大家好~今天继续给大家带来我的新周刊内容。
cover: <a href="https://github.com/Developer27149/blog/assets/23721611/3c5f8d90-980e-45c3-8e0e-f3921fed1fd8">https://github.com/Developer27149/blog/assets/23721611/3c5f8d90-980e-45c3-8e0e-f3921fed1fd8</a></h2>
<p>hello 大家好~今天继续给大家带来我的新周刊内容。</p>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/3c5f8d90-980e-45c3-8e0e-f3921fed1fd8" alt="image"></p>
<blockquote>
<p>封面图：大耗子咪咪</p>
</blockquote>
<p>OK！继续来分享上一周接触到了一些讯息！</p>
<h2>编程字体预览</h2>
<p><a href="https://www.programmingfonts.org/">Programming Fonts - Test Drive</a>：支持 <code>132</code> 种编程字体效果预览，轻松找到一款你喜欢的字体。</p>
<h2>Verctorizer AI</h2>
<p><a href="https://vectorizer.ai/">Trace Pixels To Vectors in Full Color, Fully Automatically, Using AI - Vectorizer.AI</a> 将像素图转为全彩色矢量SVG 图片的 AI 工具。</p>
<h2>清华大学计算机系课程攻略</h2>
<p><a href="https://rekcarc-tsc-uht.readthedocs.io/en/latest/index.html">Welcome to THU-CST-Cracker! — THU-CST-Cracker documentation</a>：作者收集了自己在清华计算机大学生活的学习知识资源，在网上发布了这份课程攻略，内容非常丰富。考不上清华没关系，要不要跟着人家学学试试看？</p>
<h2>CanvasStudy</h2>
<p><a href="https://827652549.github.io/CanvasStudy/">CanvasStudy | HTML5 Canvas的中文系列学习教程，也可作为查阅工具，简单上手。</a></p>
<h2>Medium Parser</h2>
<p><a href="https://github.com/Xatta-Trone/medium-parser-extension">Xatta-Trone/medium-parser-extension: Unlock medium.com using google web cache</a> 一款支持跳过 Medium 会员检测的 Chrome 插件，能让你免费阅读 medium 的文章。</p>
<h2>Harmony</h2>
<p>[haydenbleasel/eslint-config-harmony: Unified, strict and opinionated editor configuration for modern React apps. <code>Harmony</code> 是现代 <code>TypeScript</code> 应用程序的 <code>ESLint</code> 配置。它旨在与 <code>Prettier</code> 和 <code>Stylelint</code> 一起使用。</p>
<h2>Knock</h2>
<p><a href="https://knock.app/">Knock | Notifications infrastructure for developers</a> Knock 是灵活、可靠的通知基础架构，可随您一起扩展。使用我们的 API 来吸引用户、支持跨渠道工作流并管理通知首选项。</p>
<h2>最后</h2>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[珍爱生命，远离 996]]></title>
            <link>https://i5lin.top/blog/post/1991192289</link>
            <guid isPermaLink="false">1991192289</guid>
            <pubDate>Mon, 13 Nov 2023 18:11:28 GMT</pubDate>
            <description><![CDATA[最近工作直接进入 996 的节奏，感觉心脏都不舒服了]]></description>
            <content:encoded><![CDATA[<p>hello 大家好~今天继续给大家带来我的新周刊内容。</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/08/72cc7340c66fa1e6a47ef778f6eb3471.png" alt="玫瑰花对猫咪无害"></p>
<p>OK！继续来分享上一周接触到了一些讯息！</p>
<h2>Kagi</h2>
<p><img src="https://cdn.sspai.com/2023/11/05/32f926a99d42b93c13ea59d19610411b.png" alt=""></p>
<p><a href="https://type.cyhsu.xyz/2023/11/kagi/">Kagi - neverland</a>：一篇关于新生收费搜索引擎<code>Kagi</code>的分享。搜索引擎付费？你确定？</p>
<p><strong>TL;DR</strong></p>
<p><strong>The Good:</strong></p>
<ul>
<li>整洁有序、反内容农场、高度可定制的搜索结果</li>
<li>草根引擎中少见可接受的中文结果</li>
<li>完善而不招摇的 AI 功能</li>
</ul>
<p><strong>The Bad:</strong></p>
<ul>
<li>面对「生活日常」类检索的响应比较木讷</li>
<li>定价过高</li>
</ul>
<p><strong>The Ugly:</strong></p>
<ul>
<li>搜索引擎不是一个对小玩家友好的市场，能活多久除了实力也看运气</li>
</ul>
<p>...</p>
<blockquote>
<p>看完这篇文章之后，对于我这种每日都需要进行互联网搜索的用户来说，免费反而会阻碍我获取更有价值的知识，于是我立马分享给了我朋友。</p>
</blockquote>
<h2>Zod</h2>
<img src="https://zod.dev/logo.svg" style="width: 230px" /><p><a href="https://zod.dev/">Zod</a> 是一个用于构建 JavaScript 和 TypeScript 应用程序的库，主要专注于运行时类型检查和验证。</p>
<p>它提供了一种声明式的方式来定义和验证数据结构，包括对象、数组、字符串等。Zod 是由 Vercel 团队开发的，旨在帮助开发者更轻松地处理数据验证和类型安全。</p>
<p>如果你使用<code>Node.js</code>开发<code>Web API</code>系统，那么这个库可以非常方便地为你提供数据结构验证功能。</p>
<p>并且，你可以在这里得到一些教程：<a href="https://www.totaltypescript.com/tutorials/zod">Zod Tutorial | Total TypeScript</a>！</p>
<h2>System Design 101</h2>
<p><img src="https://github.com/ByteByteGoHq/system-design-101/raw/main/images/banner.jpg" alt=""></p>
<p>用图文来传授系统设计相关的知识。</p>
<h2>最后</h2>
<p>好了，今天的分享较少，笔者参与的公司项目即将结束，终于快要能从 996 的日子逃出来了。大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[程序员逃不掉的加班]]></title>
            <link>https://i5lin.top/blog/post/2002671843</link>
            <guid isPermaLink="false">2002671843</guid>
            <pubDate>Mon, 20 Nov 2023 17:23:09 GMT</pubDate>
            <description><![CDATA[加班不断，周刊易断。]]></description>
            <content:encoded><![CDATA[<p>hello 大家好~今天继续给大家带来我的新周刊内容。</p>
<blockquote>
<p>质量 &gt; 数量</p>
</blockquote>
<p><img src="https://github.com/Developer27149/blog/assets/23721611/5dd67be7-09ea-4843-b18b-7b175e6b1210" alt="image"></p>
<blockquote>
<p>封面图：毛孩子</p>
</blockquote>
<p>OK！继续来分享上一周接触到了一些讯息！</p>
<h2>Code Piper</h2>
<p><a href="https://apps.apple.com/cn/app/code-piper/id1669959741?mt=12">Mac App Store 上的“Code Piper”</a>是一款 MacOS 短信验证码复制工具。有时候我们在笔记本上需要输入手机验证码，切换、查看、复制粘贴手机或笔记本的验证码真的还不够方便，使用这个工具能让短信验证码自动提取到剪贴板，非常方便。</p>
<h2>Stackoverflow Design</h2>
<p><a href="https://stackoverflow.design/">Stacks</a>提供了在整个 <code>Stack Overflow</code> 中快速设计、构建和交付连贯体验所需的一切 - 从品牌和产品本身，到我们发送电子邮件和撰写文案的方式。读者可以从中了解到关于 <code>Stack Overflow</code>设计理念的方方面面。</p>
<h2>图拉鼎谈独立开发</h2>
<p><a href="https://www.usmacd.com/cn/tulading/">图拉鼎谈独立开发 | 安全代码</a></p>
<h2>布谷 TV</h2>
<p><a href="https://www.bugutv.org/">布谷TV | 4K电影下载-免费下载4K电影、美剧、演示片、蓝光原盘，超高清、无水印！</a></p>
<h2>最后</h2>
<p>上周说脱离<code>996</code>是假消息，还是得继续肝。</p>
<p>此外，不愿意卷的同事已经被送走了，就业环境太差了，祝福。</p>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[如何使用多个 github 账户在本地推送代码]]></title>
            <link>https://i5lin.top/blog/post/1764743198</link>
            <guid isPermaLink="false">1764743198</guid>
            <pubDate>Tue, 20 Jun 2023 06:36:48 GMT</pubDate>
            <description><![CDATA[从零开始在你的电脑上配置多个 github 账户。]]></description>
            <content:encoded><![CDATA[<p><img src="https://images.unsplash.com/photo-1647166545674-ce28ce93bdca?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80" alt=""></p>
<p>Hello，大家好。今天给大家分享一下，如何在自己的电脑上配置多个<code>github</code>账户，这样有多个账户的朋友就可以很方便地在本地互不干扰地推送代码了。</p>
<h2>账号设置</h2>
<p>前置步骤如下：</p>
<ul>
<li>前往<code>github</code>注册两个账号</li>
<li>安装<code>git</code>到本地</li>
<li>生成<code>ssh</code>密钥</li>
</ul>
<p>注册账户和安装<code>git</code>不需多言，假设我们现在注册了两个账户<code>User1</code>和<code>User2</code>，二者的注册邮箱分别是<code>user1@qq.com</code>和<code>user2@qq.com</code>。然后打开终端，我们从生成<code>ssh</code>密钥开始：</p>
<pre><code class="language-bash"># 输入命令后一直按回车即可，如果你对这些参数有疑惑，可以查找搜索引擎解释~
ssh-keygen -t rsa -b 4096 -C &quot;user1@qq.com&quot; -f ~/.ssh/user1
ssh-keygen -t rsa -b 4096 -C &quot;user2@qq.com&quot; -f ~/.ssh/user2
</code></pre>
<p>如上操作即可生成两个对应账户可用的密钥文件：</p>
<ul>
<li><code>~/.ssh/user1</code> 和 <code>~/.ssh/user1.pub</code></li>
<li><code>~/.ssh/user2</code> 和 <code>~/.ssh/user2.pub</code></li>
</ul>
<p>在生成密钥后，我们需要逐一将之复制到<code>github</code>的<a href="https://github.com/settings/keys">SSH and GPG keys</a>配置中去。</p>
<p>我们可以逐一登录账户，并且进入<a href="https://github.com/settings/keys">SSH and GPG keys</a>这个页面下，点击<code>New SSH key</code>按钮，再填写一个有助于你记忆这个密钥的信息的标题，<code>Key type</code>默认选择<code>Authentication Key</code>即可。</p>
<p>随后，将我们的密钥文件：<code>~/.ssh/user1.pub</code>的内容（<strong>注意这个密钥需要对应用户名user1</strong>，稍后复制的是<code>user2.pub</code>）复制到<code>Key</code>区域内即可。</p>
<p>📢：推荐执行以下命令复制密钥，以免复制的时候带上其他字符。（当然，你也可以通过<code>cat</code>命令查看密钥再复制，或是用文本编辑器打开密钥文件再复制）如下命令行操作如果有问题，可以用最简单的<code>cat</code>命令查看密钥，再用鼠标复制。</p>
<pre><code class="language-bash"># macOS
tr -d &#39;\n&#39; &lt; ~/.ssh/user1.pub | pbcopy
# Linux
xclip -sel clip &lt; ~/.ssh/user1.pub
# Windows
cat ~/.ssh/user1.pub | clip
</code></pre>
<p>如图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230620130115497.png" alt="image-20230620130115497"></p>
<p><strong>然后退出登录，切换另一个账户继续上面的操作，注意别复制错了密钥文件</strong>。</p>
<h2>ssh 配置</h2>
<p>首先，我们切换到<code>.ssh</code>目录下并且编辑配置文件：</p>
<pre><code class="language-bash">cd ~/.ssh
# 编辑配置文件
vim config
</code></pre>
<blockquote>
<p>你可以选择你擅长的编辑器来编辑配置文件，如果<code>config</code>文件不存在，则直接创建一个即可。</p>
</blockquote>
<p>接着编辑配置文件：</p>
<pre><code class="language-bash"># user1
Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/user1
  IdentitiesOnly yes

# user2
Host user2
  HostName github.com
  User git
  IdentityFile ~/.ssh/user2
  IdentitiesOnly yes
</code></pre>
<p>来解释一下这些配置字段：</p>
<ul>
<li>Host：SSH连接的别名，我们有多个账户，但为了方便起见我们可以将<code>user1</code>视为主号，这样我们可以直接配置<code>Host github.com</code>作为别名。在 github 仓库克隆<code>ssh</code>链接的时候，<code>github.com</code>就是默认的别名。</li>
<li>HostName：远程服务器的实际域名，显然我们需要统一设置为<code>github.com</code></li>
<li>User：连接到远程服务器时使用的用户名，对于大多数<code>Git</code>服务器来说都使用<code>git</code>作为用户名，不建议修改这个字段。当然，你想修改也是可以的，最终的身份鉴别还是根据<code>ssh</code>密钥来处理的</li>
<li>IdentityFile：密钥文件路径（注意别填写成<code>.pub</code>结尾的公钥）</li>
<li>IdentitiesOnly：仅使用<code>IdentityFile</code>指定的配置文件密钥，忽略<code>ssh-agent</code>提供的密钥。</li>
</ul>
<blockquote>
<p>ssh-agent 是一个辅助程序，用于管理和缓存<code>SSH</code>私钥，它允许您在需要时轻松地通过缓存的密钥进行身份验证，而无需每次都手动输入密码。这在使用多个 SSH 密钥和连接到多个远程服务器时非常实用。</p>
</blockquote>
<h2>git 配置</h2>
<p>在配置好<code>github</code>的公钥和<code>ssh config</code>的私钥后，我们继续配置<code>git</code>的内容。</p>
<p>首先，当我们再使用 <code>Git</code> 向 <code>GitHub</code> 推送更改时，如果您没有在本地 <code>Git</code> 环境中设置全局用户名和电子邮件地址，就会出现配置全局用户信息的提示。这是因为 Git 需要知道在提交更改时应该使用哪个用户身份。我们可以配置一个全局的用户信息：</p>
<pre><code class="language-bash">git config --global user.name &quot;user1&quot;
git config --global user.email &quot;user1@qq.com&quot;
</code></pre>
<p>配置了全局用户名和邮箱之后，在用主号操作<code>github</code>的仓库时就可以省去配置对应的用户信息。</p>
<p>并且结合之前的<code>config</code>配置，我们默认<code>Host</code>使用了<code>github.com</code>别名，那么我们就可以直接在<code>github</code>仓库下通过默认的<code>ssh</code>链接来克隆仓库了。</p>
<p>而如果我需要克隆另一个账户的代码，并且以另一个账户为身份去推送代码，那么就可以在复制<code>github</code>的<code>ssh</code>链接时修改别名。</p>
<p>举个例子：</p>
<pre><code class="language-bash"># 默认
git clone git@github.com:user2/test.git
# 修改别名为 ~/.ssh/config 的别名
git clone git@user2:user2/test.git
</code></pre>
<p>如此一来，克隆到本地的<code>test</code>仓库将会存在一个<code>.git</code>目录，此目录下的<code>config</code>文件就是仓库的<code>git</code>配置文件。</p>
<p>其内部会记录一个<code>[remote &quot;origin&quot;]</code>字段，其中的<code>url</code>将会保存好这个别名和仓库的归属人和仓库名信息。</p>
<p>为了让我们在这个项目下使用<code>push</code>等操作时能让<code>git</code>正确识别到所用的<code>github</code>账户。</p>
<p>我们可以执行一下命令：</p>
<pre><code class="language-bash"> git config --local user.name &quot;user2&quot;
 git config --local user.email &quot;user2@qq.com&quot;
</code></pre>
<p>如此一来，<code>git</code>就会将这个仓库下推送代码等操作所使用的账户信息写到<code>.git/config</code>文件中。</p>
<p>最后，我们就可以随时在不同的仓库下使用我们想要的账户去提交代码了。</p>
<h2>最后</h2>
<p>因为笔者前几天创建了两个<code>github</code>的账户来做一些事情，才有这个需求。因此，查了一些参考的文档和文章，如果能帮到大家，也是一件好事。</p>
<p>📢：如果这个配置有什么错漏，欢迎指正。</p>
<h2>参考</h2>
<ul>
<li><p><a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh">Connecting to GitHub with SSH</a></p>
</li>
<li><p><a href="https://gist.github.com/Jonalogy/54091c98946cfe4f8cdab2bea79430f9">Handling Multiple Github Accounts on MacOS</a></p>
</li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[某种意义上来说工作提供了收入，身份和意义，失业的创伤]]></title>
            <link>https://i5lin.top/blog/post/2070872891</link>
            <guid isPermaLink="false">2070872891</guid>
            <pubDate>Mon, 08 Jan 2024 16:57:44 GMT</pubDate>
            <description><![CDATA[某种意义上来说工作提供了收入，身份和意义，失业的创伤。]]></description>
            <content:encoded><![CDATA[<p>hello 大家好~今天继续给大家带来我的新周刊内容。</p>
<p><img src="https://de4965e.webp.li/blog-images/2024/08/9e9036eafa0ceff5ffac052bb8530cf3.png" alt=""></p>
<blockquote>
<p>封面图：江苏·秋冬·红色的枫叶</p>
</blockquote>
<p>🕊🕊🕊🕊🕊🕊🕊🕊🕊🕊🕊🕊🕊🕊🕊🕊🕊</p>
<p>OK！继续来分享上一周接触到了一些讯息！</p>
<h2>perplexity.ai</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20240108230502068.png" alt="image-20240108230502068"></p>
<p><code>Perplexity.ai</code> 是一个聊天机器人式的<code>搜索引擎</code>，利用人工智能技术以自然语言提供问题的答案。它由大型语言模型提供支持，旨在帮助研究人员、作家、艺术家、音乐家和程序员等各种专业人士完成<code>回答问题</code>、<code>生成文本</code>、<code>撰写创意内容</code>和<code>总结文本</code>等任务。</p>
<p>最近还拿到了融资，发展得很好。</p>
<h2>Devv</h2>
<p><img src="https://github.com/devv-ai/devv/raw/main/assets/devv-twitter-bio.png" alt=""></p>
<p><a href="https://devv.ai/zh">Devv</a> 号称最懂程序员的新一代 <code>AI</code> 搜索引擎，见到挺多开发同行都提到这个引擎对代码的解释会比 ChatGPT 官方的回答细腻一些，作者是国人，支持一下！</p>
<h2>FireFox 多账户容器</h2>
<p><a href="https://support.mozilla.org/en-US/kb/containers">Multi-Account Containers | Firefox Help</a>：火狐用户可以在扩展商店搜索此插件，此插件深度支持一个多账户容器模式，它允许用户在同一浏览器窗口中创建多个容器，每个容器可以独立存储特定网站的 Cookie 和站点数据，从而实现在不同容器中登录同一网站的不同账号，或者将不同类型的网页活动分开。</p>
<p>也就是说用户可以更好地管理其在线活动，避免不同网站之间的数据共享，同时也能够更轻松地切换不同的在线身份和账号。笔者用这个模式同时创建了一个日常 youtube 账户和一个纯英文环境的 youtube 账户。</p>
<h2>产品 UI 创意借鉴</h2>
<p>前段时间在设计我的 Chrome 插件 <code>TabX</code> 的时候，看着自己的 UI 却想不出优化的点，属实是创意枯竭了。于是我立马去问了问千里之外的友人：“你经常去哪抄袭人家的设计？”</p>
<p>答曰：</p>
<ul>
<li><code>Behance</code>: <a href="https://www.behance.net/">Search Projects :: Photos, videos, logos, illustrations and branding :: Behance</a></li>
<li><code>Pinterest</code></li>
<li><code>Dribble</code></li>
</ul>
<p>如果你需要借鉴一下他人的设计，这其实也是可以的。</p>
<h2>青石 bluestone</h2>
<p><a href="https://www.bluemd.me/">Bluestone - markdown editing and sharing tools</a> 一款所见即所得的 markdown 编辑器，集成了 Mermaid 图形与 Katex 公式，支持明亮和暗黑风格，另外的亮点在于你可以使用这个工具配合其教程轻松将编辑的文本推送到远程，分享给其他人。</p>
<p>这个编辑器目前处于高速发展阶段，相比于付费产品<code>typora</code>来说，功能还是需要好好打磨和迭代，如果你需要一款免费的所见即所得的 <code>markdown</code>编辑器，可以体验体验。</p>
<h2>Electron Playground</h2>
<p><img src="https://github.com/tal-tech/electron-playground/raw/master/resources/readme/01.gif" alt=""></p>
<p><a href="https://github.com/tal-tech/electron-playground">tal-tech/electron-playground: This is a project to quickly experiment and learn electron related APIs</a> 一个快速尝试和学习<a href="https://github.com/electron/electron">electron</a>的项目，对electron的api进行了归纳和总结，对常用的业务功能做了demo演示。</p>
<p>我们可以学习、体验、调试和编辑代码，以此来掌握<code>Electron</code>开发的技术点。</p>
<h2>Vue TSX Admin</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20240108234140678.png" alt="image-20240108234140678"></p>
<p><a href="https://github.com/manyuemeiquqi/vue-tsx-admin">manyuemeiquqi/vue-tsx-admin: About a modern Vue Admin. It is based on Vue3 and TSX，It&#39;s flexible</a>，适用于需要快速创建<code>Vue3+TS</code>后台系统的场景。</p>
<h2>Picview</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20231205231916866.png" alt="image-20231205231916866"></p>
<p><a href="https://apps.apple.com/us/app/picview-image-photo-browser/id6452016140?mt=12">Picview - Image Photo Browser on the Mac App Store</a> 是一款 MacOS 预览看图应用，界面和交互非常精美。</p>
<h2>react-color</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20231218221946301.png" alt="image-20231218221946301"></p>
<p><a href="https://casesandberg.github.io/react-color/">React Color</a> 颜色选择器组件，功能非常完整。</p>
<h2>React-colorful</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20231218224201399.png" alt="image-20231218224201399"></p>
<p><a href="https://omgovich.github.io/react-colorful/">react-colorful</a> 相比上面的<code>React color</code>组件，这个仅<code>2.8kb</code>大小，并且没有任何依赖项，缺点是功能少了些，但是也能满足较轻的颜色选择需求。</p>
<h2>Icon Horse</h2>
<p><a href="https://icon.horse">Icon Horse</a> 是一个图标解析服务，用户提供一个域名，他们返回此域名的 LOGO，如果不存在则返回默认 Logo 图标，这对于需要在应用产品开发中使用其他应用图标的场景，确实非常方便，开发者可以不用考虑自己去解决高质量应用图标的资源问题。</p>
<p>ps：免费月请求 1000 个图标，其他方案略贵（对于苦哈哈独立开发者而言）。</p>
<h2>Gemini Openai Proxy</h2>
<p><a href="https://github.com/zhu327/gemini-openai-proxy">Gemoni 转 OpenAI</a>：这是一个开源的谷歌 Gemini API 转 OpenAI 协议的网络代理服务，我们可以轻松使用 Docker 部署在本地或服务器上，然后在某些需要使用 OpenAI 的应用上配置这个服务的接口地址来使用 AI 相关的功能，比如我非常喜欢用<code>ChatGPT Translation</code> 翻译英文内容。如果使用遇到问题，还是需要看看文档。</p>
<h2>暴论 😂</h2>
<ul>
<li>代码注释是一种道歉，为未选择更清晰的名称或更合理的参数而道歉，为代码无法维护而道歉，为不使用知名算法而道歉，为编写&quot;聪明&quot;代码而道歉，为没有良好的版本控制系统而道歉，为未完成代码编写工作而道歉，为留下漏洞或代码中的缺陷而道歉。</li>
</ul>
<h2>最后</h2>
<p>周一鸽了，周二补上。</p>
<blockquote>
<p>非常佩服那些能坚持不懈，从不拖更的作者 😂</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[前端 icon 图标解决方案]]></title>
            <link>https://i5lin.top/blog/post/2122658339</link>
            <guid isPermaLink="false">2122658339</guid>
            <pubDate>Wed, 07 Feb 2024 10:10:00 GMT</pubDate>
            <description><![CDATA[图标是 web 应用中常见的视觉元素之一，一直以来我都在寻找合适自己的图标解决方案，今天来介绍一下我最常用的一个方案。]]></description>
            <content:encoded><![CDATA[<p>图标是 web 应用中常见的视觉元素之一，一直以来我都在寻找合适自己的图标解决方案，今天来介绍一下我最常用的一个方案：<code>tailwindcss + icon plugin</code> !</p>
<img width="994" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/01f318ef-0ddf-446b-aa83-062fdbcd3cce"><h2>Tailwindcss</h2>
<blockquote>
<p>如果你已经很熟悉<code>tailwindcss</code> ，可以跳过 😂</p>
</blockquote>
<p>在图标之前，先介绍一下<code>tailwindcss</code>: <a href="https://tailwindcss.com/"><a href="https://tailwindcss.com/">Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.</a></a></p>
<p>使用这个工具，开发者只需要写某些类名即可，每一个类名都会在构建时编译到最终的样式表中。</p>
<p>这就意味着我们可以在整个项目中随处写类名，最后只会有一个类名及其样式存在于打包的样式表中。</p>
<p>看看下面这个例子：</p>
<pre><code class="language-bash">&lt;figure class=&quot;md:flex bg-slate-100 rounded-xl p-8 md:p-0 dark:bg-slate-800&quot;&gt;
  &lt;img class=&quot;w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto&quot; src=&quot;/sarah-dayan.jpg&quot; alt=&quot;&quot; width=&quot;384&quot; height=&quot;512&quot;&gt;
  &lt;div class=&quot;pt-6 md:p-8 text-center md:text-left space-y-4&quot;&gt;
    &lt;blockquote&gt;
      &lt;p class=&quot;text-lg font-medium&quot;&gt;
        “Tailwind CSS is the only framework that I&#39;ve seen scale
        on large teams. It’s easy to customize, adapts to any design,
        and the build size is tiny.”
      &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;figcaption class=&quot;font-medium&quot;&gt;
      &lt;div class=&quot;text-sky-500 dark:text-sky-400&quot;&gt;
        Sarah Dayan
      &lt;/div&gt;
      &lt;div class=&quot;text-slate-700 dark:text-slate-500&quot;&gt;
        Staff Engineer, Algolia
      &lt;/div&gt;
    &lt;/figcaption&gt;
  &lt;/div&gt;
&lt;/figure&gt;
</code></pre>
<p>你可以通过类名写几乎所有样式，<code>tailwindcss</code> 支持良好的媒体查询功能和主题和自定义样式。</p>
<p>最重要的是 <code>tailwindcss</code> 具有良好的插件系统和生态，官方的功能对动画的支持比较薄弱，但是在社区里存在<a href="https://www.tailwindcss-animated.com/"><a href="https://www.tailwindcss-animated.com/">Tailwind CSS Animated</a></a> 这样的插件，我们可以比较方便的用来写基础的动画。</p>
<blockquote>
<p>只要对组件进行良好的拆分，其实最终的类名也不会太长，况且我们还可以组合预处理器来使用。</p>
</blockquote>
<p>如果你需要更好的动画，则可以使用专业的动画库来实现相应的需求，例如：</p>
<ul>
<li><a href="https://www.framer.com/motion/"><a href="https://www.framer.com/motion/">Documentation | Framer for Developers</a></a></li>
<li><a href="https://gsap.com/docs/v3/GSAP/"><a href="https://gsap.com/docs/v3/GSAP/">GSAP | GSAP | Docs &amp; Learning</a></a></li>
<li><a href="https://animejs.com/"><a href="https://animejs.com/">anime.js • JavaScript animation engine</a></a></li>
<li><a href="https://www.react-spring.dev/"><a href="https://www.react-spring.dev/">react-spring</a></a></li>
<li><a href="https://threejs.org/"><a href="https://threejs.org/">Three.js – JavaScript 3D Library</a></a>（杀手级）</li>
</ul>
<p>每一个动画库都非常强大，或许选择其中一个进行深度使用就好。</p>
<h2>Icon</h2>
<p>我们可以使用<code>tailwindcss</code> 的插件 <a href="https://github.com/iconify/iconify/tree/main/plugins/tailwind"><a href="https://github.com/iconify/iconify/tree/main/plugins/tailwind">iconify/plugins/tailwind at main · iconify/iconify</a></a> 和 <a href="https://icones.js.org/"><a href="https://icones.js.org/">Icônes</a></a> 配合解决图标需求。</p>
<p>在按官方文档安装好插件之后，直接在 <a href="https://icones.js.org/"><a href="https://icones.js.org/">Icônes</a></a> 上搜索关键词就好。</p>
<p>在之前，我长期使用<a href="https://react-icons.github.io/react-icons/"><a href="https://react-icons.github.io/react-icons/">React Icons</a></a>作为图标解决方案，上面的图标种类非常齐全（仅部分截图）：</p>
<img width="785" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/e9730ee8-0377-4673-8804-0c4ba145db3b"><p>而在我切换到<code>Vue</code> 技术栈之后，就无法再用这一个库了，机缘巧合下找到了<a href="https://icones.js.org/"><a href="https://icones.js.org/">Icônes</a></a>，这个站点是托尼老师开发的，资源非常丰富（部分截图）：</p>
<img width="1099" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/8a0c00ab-b648-47c9-9c55-64e1be33ce57"><p>相比之前的库，这里有更多的资源，包括一些我很喜欢的动画图标。</p>
<blockquote>
<p><a href="https://react.useanimations.com/"><a href="https://react.useanimations.com/">UseAnimations – React micro-animations library</a></a> 少量的动画图标推荐</p>
</blockquote>
<p>话说回来，使用<a href="https://icones.js.org/"><a href="https://icones.js.org/">Icônes</a></a>再配合上作者开发的<a href="https://marketplace.visualstudio.com/items?itemName=antfu.iconify"><a href="https://marketplace.visualstudio.com/items?itemName=antfu.iconify">Iconify IntelliSense - Visual Studio Marketplace</a></a>插件，我们可以在<code>vscode</code> 里直接预览图标的效果，举个例子：</p>
<img width="398" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/62147a18-5831-469a-8d05-146b7980bf20"><p>最后，使用<code>tailwindcss + icon 插件</code> 已经能满足我大多数关于样式和图标的需求。</p>
<p>在一些特殊的样式需求下，我还是可以用预处理器去写样式。在一些特殊的图标动画需求下，我们也可以使用专业的动画库，甚至是 Lottie 来实现动画效果（<a href="https://lottiefiles.com/"><a href="https://lottiefiles.com/">LottieFiles: Download Free lightweight animations for website &amp; apps.</a></a>）</p>
<img width="1031" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/65665f00-aee2-4251-8adf-5847412db987"><p>相较于<code>gif</code> 方案，后者体积可以变得非常小，也提供了很多控制机制来满足用户的交互需求。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[时不我待，荒废可耻]]></title>
            <link>https://i5lin.top/blog/post/2261752519</link>
            <guid isPermaLink="false">2261752519</guid>
            <pubDate>Wed, 24 Apr 2024 16:53:28 GMT</pubDate>
            <description><![CDATA[我是一只飞回来的鸽子 🕊]]></description>
            <content:encoded><![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/9870E7BD-9F76-4340-BD74-15EFE91579FB_1_105_c.jpeg" alt="9870E7BD-9F76-4340-BD74-15EFE91579FB_1_105_c"></p>
<p>OK！继续来分享上<code>N</code>周接触到了一些讯息！</p>
<h2>Synergy</h2>
<p><a href="https://symless.com/synergy"><a href="https://symless.com/synergy">Synergy - Share one mouse &amp; keyboard across computers</a></a>：免费的局域网内的跨设备键鼠共享软件，在不同的设备上安装这个软件，再通过很简单的地址配置即可在局域网内共享键盘鼠标和剪贴板。</p>
<p>我终于可以在自己的笔记本键盘上直接操作另外一台笔记本了，省去了买对拷线的钱和在另一台电脑上操作鼠标键盘、复制内容的麻烦。</p>
<p><img src="https://assets-global.website-files.com/5cb7274cc3cc0c1bc1771305/6476f4e878f4f4bae77fc713_screenlayout.gif" alt=""></p>
<p>如需下载，可以前往：<a href="https://github.com/DEAKSoftware/Synergy-Binaries"><a href="https://github.com/DEAKSoftware/Synergy-Binaries">DEAKSoftware/Synergy-Binaries: Download the latest stable Synergy binaries.</a></a> </p>
<h2>tailwind-merge</h2>
<img src="https://github.com/dcastil/tailwind-merge/raw/v2.3.0/assets/logo.svg" style="width: 100px" /><p><a href="https://github.com/dcastil/tailwind-merge"><a href="https://github.com/dcastil/tailwind-merge">dcastil/tailwind-merge: Merge Tailwind CSS classes without style conflicts</a></a>：用于合并 Tailwind CSS 类，且在 JS 中不会产生样式冲突。</p>
<p>简单举个例子来说明为什么推荐了解这个库。</p>
<blockquote>
<p>使用 TypeScript 和 TailwindCSS</p>
</blockquote>
<p>我们首先来编写一个按钮组件：</p>
<pre><code class="language-react">import { PropsWithChildren } from &#39;react&#39;

interface ButtonProps extends PropsWithChildren {
  className?: string
}
export default function Button({ className, children }: ButtonProps) {
  return &lt;button className={`rounded rounded-md p-2 ${className}`}&gt;{children}&lt;/button&gt;
}
</code></pre>
<p>上述的例子可以给按钮一些默认的样式，以及添加自定义的类名来实现更多样式功能。但是也存在一些问题，例如：</p>
<ul>
<li>没有处理 <code>undefined</code>的特殊情况</li>
<li>仅在语法层面对传入的值进行类型标记，不够严谨</li>
<li>传入的类名会和默认的类名同时存在，产生代码冗余甚至是样式冲突。假设传入的 className 为 <code>rounded-lg</code>，最终无效的<code>rounded-md</code>也会生成到最终的样式表中去。为何我们喜欢<code>TailwindCSS</code>？或许有一点，就是重复利用类名的机制能让样式表非常轻巧。</li>
</ul>
<p>利用<code>tailwind-merge</code>我们可以非常方便地解决这个问题：</p>
<pre><code class="language-typescript">import { twMerge } from &#39;tailwind-merge&#39;

twMerge(&#39;px-2 py-1 bg-red hover:bg-dark-red&#39;, &#39;p-3 bg-[#B91C1C]&#39;)
// → &#39;hover:bg-dark-red p-3 bg-[#B91C1C]&#39;
</code></pre>
<p>如上所示，<code>twMerge</code>函数为我们实现了属性合并和去重的逻辑，甚至是能将一些复杂的类名简化，例如：</p>
<pre><code class="language-typescript">twMerge(&#39;left-0 top-0 right-0 bottom-0&#39;)
// 最终变成 inset-0
</code></pre>
<blockquote>
<p>即使是经验丰富的开发者，也可能会在编写 tailwindcss 类名的时候写出复杂的代码，或许自动对类名进行简化的 <code>eslint</code> 插件会是一个很好的选择。</p>
</blockquote>
<p>与此同时，<code>clsx</code>这个库也经常被大家使用于类名逻辑处理。</p>
<pre><code class="language-react">import { PropsWithChildren } from &#39;react&#39;
import clsx from &#39;clsx&#39;

interface ButtonProps extends PropsWithChildren {
  className?: string
}
export default function Button({ className, children }: ButtonProps) {
  return &lt;button className={clsx(&#39;rounded rounded-md p-2&#39;, className)}&gt;{children}&lt;/button&gt;
}
</code></pre>
<p><code>clsx</code>能帮我们处理空字符串和<code>undefined</code>传值的问题，甚至我们可以给<code>className</code>一个更好的类型定义来充分发挥<code>clsx</code>的能力。</p>
<pre><code class="language-react">import { ClassValue, clsx } from &quot;clsx&quot;
import { twMerge } from &quot;tailwind-merge&quot;

export function cn(...inputs: ClassValue[]) {
return twerge(clsx(inputs))
}
</code></pre>
<blockquote>
<p>PS:  推荐用 <a href="https://github.com/tailwindlabs/prettier-plugin-tailwindcss"><a href="https://github.com/tailwindlabs/prettier-plugin-tailwindcss">tailwindlabs/prettier-plugin-tailwindcss: A Prettier plugin for Tailwind CSS that automatically sorts classes based on our recommended class order.</a></a> 插件对 tailwindcss 类名进行排序，亦或是<a href="https://marketplace.visualstudio.com/items?itemName=heybourn.headwind"><a href="https://marketplace.visualstudio.com/items?itemName=heybourn.headwind">Headwind - Visual Studio Marketplace</a></a> 这个 vscode 插件来自动排序。</p>
</blockquote>
<p>快将这个函数放到你的武器库中去吧！</p>
<h2>useAnimations</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/El170D.png" alt="img"></p>
<p><a href="https://react.useanimations.com/"><a href="https://react.useanimations.com/">UseAnimations – React micro-animations library</a></a> 提供少数动画图标，每一个都有交互动画，优美有趣且实用，但是数量很少。</p>
<h2><a href="https://fontsource.org/">Fontsource</a></h2>
<p><a href="https://fontsource.org/">Fontsource</a> （字体资源包）项目简化了使用 Google Fonts 和其他开源字体的使用。它提供了 npm 模块，你可以为要使用的字体安装，非常方便。</p>
<h2>React type animation</h2>
<p><a href="https://react-type-animation.netlify.app/"><a href="https://react-type-animation.netlify.app/">React Type Animation</a></a> 是一个非常漂亮的 <code>React</code>文字输入动画库，你可以轻松用来创建打字机效果的文字描述动画。</p>
<h2>IOS 开发教程</h2>
<p><a href="https://cs193p.sites.stanford.edu/2023"><a href="https://cs193p.sites.stanford.edu/2023">斯坦福大学的 IOS 开发教程</a></a>，图文并茂，在世界互联网的任何一个角落即可免费学习世界顶尖大学的开发教程。</p>
<h2>React email</h2>
<p><a href="https://react.email/"><a href="https://react.email/">React Email</a></a>： 如果你需要为应用添加邮件相关的功能，那么这个库绝对值得了解一下。<code>React email</code>支持组合官方提供的一系列基础<code>React</code>组件渲染成<code>HTML</code>文本，因此我们可以非常方便地使用<code>React</code>设计发送给用户的邮件内容，此外官方文档也写得非常齐全，绝对是开箱即用。</p>
<blockquote>
<p><code>vue</code>也有类似的库，唯一需要注意的是部分 HTML 支持度不完整，例如图片格式不支持 SVG 等。</p>
</blockquote>
<p>在后端发送邮件，可以参考官方提供的以下方案：</p>
<ul>
<li>Resend</li>
<li>Nodemailer</li>
<li>SendGrid (推荐)</li>
<li>AWS SES</li>
<li>...</li>
</ul>
<h2>Earthly</h2>
<p><a href="https://earthly.dev/"><a href="https://earthly.dev/">Earthly - Make Builds Super Simple</a></a>：本地 CI/CD 开源工具，简洁易用。</p>
<h2>timeago.js</h2>
<p><a href="https://github.com/hustcc/timeago.js/tree/master">hustcc/timeago.js: :clock8: timeago.js is a tiny(2.0 kb) library used to format date with `*** time ago` statement.</a> </p>
<p>仅 <code>2kb</code> 的相对日期计算库，可以方便的计算某个时间到现在的相对时间，充足的测试用例可以让你放心使用。</p>
<h2>开源能力查询</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20240424233750220.png" alt="image-20240424233750220"></p>
<p><a href="https://gitroll.io/"><a href="https://gitroll.io/">GitRoll | Learn your candidate&#39;s real coding skills in minutes.</a></a> 通过扫描目标用户的 <code>github</code> 代码，分析用户的开源能力信息。</p>
<p><strong>&quot;听说你在简历上写着热衷开源？&quot;</strong>，这下可以直接看看实力了。</p>
<h2><a href="https://www.hyperui.dev/">Hyper UI</a></h2>
<p><a href="https://www.hyperui.dev">Hyper UI</a> 分享了一系列基于<code>tailwindcss</code>的组件和源代码，可供开发者快速实现一些 UI 效果。</p>
<h2>最后</h2>
<p>好久没有记录一些内容了，坚持一件事真的很难，不过歇息了一阵子依然可以再出发。好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[想要钓鱼就要去鱼多的地方，想要赚钱就要将可以赚钱的方法重复实践]]></title>
            <link>https://i5lin.top/blog/post/2355907288</link>
            <guid isPermaLink="false">2355907288</guid>
            <pubDate>Sun, 16 Jun 2024 18:13:48 GMT</pubDate>
            <description><![CDATA[飞机耳 + 玉足]]></description>
            <content:encoded><![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20240617012026938.png" alt="飞机耳 + 玉足"></p>
<p>OK！继续来分享上一周接触到的一些讯息！</p>
<h2>Dozer</h2>
<p><img src="https://github.com/Mortennn/Dozer/raw/master/Stuff/demo.gif" alt=""> </p>
<p><a href="https://github.com/Mortennn/Dozer">Mortennn/Dozer: Hide menu bar icons on macOS</a> 是一个隐藏<code>macOS</code>菜单栏图标的开源程序，笔者体验下来内存占用为<code>45 M</code>左右，比此前自己使用的同类应用都小。</p>
<h2>NanaZip</h2>
<p><img src="https://github.com/M2Team/NanaZip/raw/main/Documents/ContextMenu.png" alt=""></p>
<p><a href="https://github.com/M2Team/NanaZip">M2Team/NanaZip: The 7-Zip derivative intended for the modern Windows experience</a> 7 - Zip 衍生的开源压缩和解压缩<code>Windows</code>软件，装机必备。</p>
<h2>Shiki 代码演示</h2>
<p><a href="https://github.com/shikijs/shiki-magic-move">GitHub - shikijs/shiki-magic-move: Smoothly animated code blocks with Shiki </a>是一个具有精美动画效果的代码演示工具，可以用来写一些教程或文档，甚至是面向开发者的特殊应用。使用<code>shikijs</code>可以展示非常精美的代码变化效果。</p>
<h2>Moria</h2>
<p><img src="https://github.com/s1ntoneli/Moria-dist/raw/main/Screenshot.png" alt=""></p>
<p><a href="https://github.com/s1ntoneli/Moria-dist">s1ntoneli/Moria-dist: Moria 分发</a> 是一个 Macos 平台的应用，可以<strong>在任意 App 中，快速搜索、执行菜单中的命令，节约时间</strong>！基于原生的菜单功能，通过全局快捷键<code>⌘+Shift+o</code>快速搜索当前应用的所有菜单功能，因此我们可以利用这个工具摆脱对鼠标的依赖，快速定位到菜单中的命令中去。<img src="https://github.com/s1ntoneli/Moria-dist/raw/main/moria-zh.gif" alt=""></p>
<h2>KeyCastr</h2>
<p><img src="https://github.com/keycastr/keycastr/raw/main/assets/mouse-click-visualizer.gif" alt=""></p>
<p><a href="https://github.com/keycastr/keycastr">keycastr/keycastr: KeyCastr, an open-source keystroke visualizer</a> 是一款开源的键盘镜像软件，可以在你输入的时候将键盘按键实时地显示在屏幕上，如果你想要展示自己的键盘操作，这是一个不错的选择。</p>
<h2>Tailwindcss 变体组合</h2>
<blockquote>
<p>类名变体笔者最初在<code>windicss</code>上看到，后续也在<code>unocss</code>中看到此插件，但是<code>tailwindcss</code>官方始终没有发布自己的插件</p>
</blockquote>
<p><a href="https://github.com/brandonmcconnell/tailwindcss-multi">brandonmcconnell/tailwindcss-multi: Variant-grouped utility support for Tailwind CSS</a>是一个<code>tailwindcss</code>的转换器插件，可以将如下类名进行转换：</p>
<pre><code class="language-jsx">&lt;div class=&quot;hover:multi-[bg-red-500;text-white]&quot;&gt;
  When hovered, this text is white and the background is red.
&lt;/div&gt;
</code></pre>
<p>转为:</p>
<pre><code class="language-jsx">&lt;div class=&quot;hover:bg-red-500 hover:text-white&quot;&gt;
  When hovered, this text is white and the background is red.
&lt;/div&gt;
</code></pre>
<p>需要注意的是，由于<code>tailwindcss</code>的破坏性更新，如果需要在<code>multi</code>内部使用嵌套和冒号，则需要额外添加<code>[]</code>。</p>
<h2>33 个技能点</h2>
<p><a href="https://github.com/stephentian/33-js-concepts">stephentian/33-js-concepts: :scroll: 每个 JavaScript 工程师都应懂的33个概念 @leonardomso</a></p>
<p>这个项目是为了帮助开发者掌握 <code>JavaScript</code> 概念而创立的。它不是必备，但在未来学习（<code>JavaScript</code>）中，可以作为一篇指南。</p>
<h2>Notion 风格的头像创作</h2>
<p><a href="https://notion-avatar.vercel.app/zh">Notion 风格头像制作</a>：可以在这里选择不同的贴图最终创建一个自己喜欢的<code>notion style</code>的头像</p>
<h2>IOS 开发课程</h2>
<p><a href="https://www.bilibili.com/video/BV1FG411J7b9/?spm_id_from=333.999.0.0&vd_source=657d9351ae87e8da9ec0f1670d72d27c">赵纯想最新录制｜《iOS开发课程-新手零基础版》Swift5.5 iOS16 SwiftUI4.0｜ 苹果iOS软件开发教程_哔哩哔哩_bilibili</a></p>
<h2>在线工具箱 - 腾讯出品</h2>
<p><a href="https://tool.browser.qq.com/">帮小忙，腾讯QQ浏览器在线工具箱平台</a> ：腾讯也来吃这口饭了，非常全面的工具箱，不过有些功能依然需要满足腾讯特色企业精神（充钱）。</p>
<h2>Git 从入门到应付日常工作</h2>
<p><a href="https://wgzhao.github.io/notes/misc/git-from-zero-to-hero/">Git从入门到应付日常工作 - 个人笔记</a> 这是一篇网上某作者的<code>Git</code>知识分享文档，对于日常使用<code>Git</code>的开发者来说，我个人感觉可以一看，或许对你有所帮助。</p>
<h2>最后</h2>
<p>六月十二日是我对象木木生日，祝她生日快乐！</p>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[【译文】TanStack Query: 在变更之后自动使查询失效]]></title>
            <link>https://i5lin.top/blog/post/2402228975</link>
            <guid isPermaLink="false">2402228975</guid>
            <pubDate>Thu, 11 Jul 2024 04:36:47 GMT</pubDate>
            <description><![CDATA[我喜欢 Tanstack query`但是在使用过程中遇到了 mutation 之后自动 invalidate 数据的问题，于是搜索到一篇博客，本文是这篇博客的翻译内容。]]></description>
            <content:encoded><![CDATA[<p><code>查询</code>（Query）和<code>变更</code>（Mutation）就像是一枚硬币的两面。<code>查询</code>（Query）定义了一个异步资源用于读取，这通常来源于数据获取。而<code>变更</code>（Mutation）则是用于更新此类资源的操作。</p>
<p>当一个<code>变更</code>（Mutation）完成时，它很可能会影响<code>查询</code>（Query）。例如，变更一个<code>issue</code>很可能会影响<code>issue</code>列表。因此，有些人可能会觉得奇怪，为什么不把<code>Query</code>和<code>Mutation</code>关联起来？</p>
<p>原因其实很简单：<code>TanStack Query</code>完全没有主观地要求你如何管理自己的资源，并非每个人都喜欢在<code>mutation</code>之后重新获取数据。</p>
<p>有些情况下，我们希望将<code>mutation</code>返回的数据手动放进缓存中，以避免一次网络请求。此外还有很多不同的方法来执行<code>invalidate</code>（无效化）操作。</p>
<blockquote>
<p>Tanstack Query 将 query 的数据缓存起来，开发者可以通过 invalidate 行为让其失效，从而自动执行 queryFn 请求最新的数据</p>
</blockquote>
<ul>
<li>你会在<code>onSuccess</code>还是<code>onSettled</code>回调函数中执行<code>invalidate</code>操作？前者仅在变更成功之后调用，后者则在出错的时候也会调用。</li>
<li>你想要等待 <code>invalidations</code>结束吗？等待<code>invalidate</code>行为结束的话将会保持<code>mutation</code>的挂起状态，直到<code>invalidate</code>结束（通常是<code>refetch</code>资源数据）。这是可能是一件好事，例如，如果你希望在提交模态框表单之后等待列表数据更新再关闭模态框。但是，如果你确定想要<code>提交成功后立即关闭模态框</code>，甚至可能会导致用户一开始因为网络请求的原因可能看不到最新的数据而感到惊奇，那么就没必要等待<code>invalidations</code>结束。</li>
</ul>
<p>由于没有一种普适的解决方案，<code>Tanstack Query</code>并不提供对应开箱即用的功能。然而，感谢<code>Tanstack Query</code>支持全局缓存回调功能，在<code>Tanstack Query</code>中实现你想要的自动无效化并不难。</p>
<h3>全局缓存回调（The Global Cache Callbacks）</h3>
<p><code>变更</code>（Mutations）有回调函数——<code>onSuccess</code>、<code>onError</code> 和 <code>onSettled</code>，这些回调函数需要在每个单独的 <code>useMutation</code> 中定义。此外，这些回调函数也存在于 <code>MutationCache</code> 中。每一个<code>queryClient</code>实例在创建的时候都可以配置一个<code>mutationCache</code>，这个<code>mutationCache</code>里的回调函数会在每一个<code>mutation</code>调用之后执行（并且会在单独定义的<code>mutation</code>参数中定义的回调之前执行）。</p>
<p>如下所示：</p>
<pre><code class="language-js">import { QueryClient, MutationCache } from &#39;@tanstack/react-query&#39;

// 创建 MutationCache 并提供回调函数
const mutationCache = new MutationCache({
  onSuccess: (data, variables, context, mutation) =&gt; {
    console.log(&#39;Mutation was successful&#39;);
    if(Array.isArray(mutation?.meta?.awaits)) {
      return queryClient.invalidateQuires({
          queryKey: mutation.meta.awaits	        
        },{ cancelRefetch: false })
    }
  },
  onError: (error, variables, context) =&gt; {
    console.error(&#39;Mutation failed&#39;);
  },
  onSettled: (data, error, variables, context) =&gt; {
    console.log(&#39;Mutation settled&#39;);
  }
});

// 创建 QueryClient 并传入自定义的 MutationCache
const queryClient = new QueryClient({
  mutationCache,
  // 其他选项
});
</code></pre>
<p>这些回调函数接收的参数与<code>useMutation</code>中的参数相同，只是它们还会接收<code>变更实例</code>（Mutation instance）作为最后一个参数。而且与通常的回调函数一样，返回的 <code>Promise</code> 会被等待。</p>
<p>那么如何借此实现自动<code>invalidation</code>呢？我们只需要在全局回调函数内部调用<code>queryClient.invalidateQueries()</code>就行了：</p>
<pre><code class="language-js">const queryClient = new QueryClient({
  mutationCache: new MutationCache({
    onSuccess: () =&gt; {
      queryClient.invalidateQueries()
    },
  }),
})
</code></pre>
<p>通过上面的五行代码，我们得到了在 Remix（抱歉，React-Router）中相似的行为：在每次提交之后让一切失效。向你致敬，Alex 展示了这一思路：<a href="https://x.com/alexdotjs/status/1744467890277921095"><a href="https://x.com/alexdotjs/status/1744467890277921095">Alex / KATT 🐱 on X: &quot;@housecor I just invalidate everything on every mutation https://t.co/0TALY8NdrV&quot; / X</a></a></p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20240620173116614.png" alt="image-20240620173116614"></p>
<h3>但这是不是有点激进了？</h3>
<p>可能是，也可能不是。这得看情况。再强调一次，为什么没有内置这个机制，因为有很多方法可以实现这一点。在这里我们需要澄清一点，<code>无效化并不总是等同于重新获取数据</code>。</p>
<p><code>Invalidation</code>仅仅是重新获取所有匹配的活跃查询，并将其余标记为<code>stale</code>（过期），这样它们在下次使用时会被重新获取。</p>
<p>这通常是一个很好且折衷的方案。想想你现在有一个带有筛选条件的<code>issue</code>列表，由于你将过滤条件对象作为<code>queryKey</code>的一部分，每次筛选条件对象变化都会生成不同<code>queryKey</code>的缓存记录，然而我们每次只渲染一个筛选对象对应的结果。重新获取所有数据会产生大量当前不必要的请求，我们也没法保证用户会修改筛选条件来让查看对应的结果，从而让这些请求有价值。</p>
<p>因此，<code>invalidation</code> 只重新获取我当前在屏幕上看到的内容（活跃的查询），以获取最新的视图，而其余的内容将在需要时重新获取。</p>
<h3>尝试让特定的查询失效</h3>
<p>好吧，精细化验证是有必要的。变更<code>issue</code>列表的数据后让个人资料的数据失效从而触发其重新获取个人资料？？？这真的没什么意义可言。</p>
<p>再说一次，这需要权衡考虑。如果你明确知道需要重新获取什么数据，那么精细化控制让哪些<code>Query</code>失效是一个很好的实践方案。如果你希望代码尽量简单，宁愿频繁获取多一些数据也不希望错过重新回去的机会，那么重新获取所有匹配的活跃查询，并将其余标记为<code>stale</code>（过期）也可以。</p>
<p>过去，我们经常进行精细化重新验证，结果发现稍后需要添加的另一个资源与使用的失效模式不匹配。在那时，我们不得不遍历所有变化回调函数，看看是否需要重新获取该资源。这样做很麻烦且容易出错。</p>
<p>此外，我们通常为大多数查询使用中等大小的<code>staleTime</code>（大约2分钟）。因此，在无关的用户交互后进行失效的影响可以忽略不计。</p>
<p>当然，你可以使你的逻辑更复杂，使重新验证更智能化。以下是我过去使用过的一些技术：</p>
<h3>将其与<code>mutationKey</code>相关联。</h3>
<p><code>MutationKey</code>和<code>QueryKey</code>之间没有共同点，而且<code>MutationKey</code>也是可选的。如果需要的话，你可以通过使用<code>MutationKey</code>来指定应该失效哪些查询，从而将它们关联起来。</p>
<blockquote>
<p>如果你不需要标记一个 <code>mutation</code>，那么定义的时候不提供<code>mutationKey</code>也无所谓。</p>
</blockquote>
<pre><code class="language-js">const queryClient = new QueryClient({
  mutationCache: new MutationCache({
    onSuccess: (_data, _variables, _context, mutation) =&gt; {
      queryClient.invalidateQueries({
        queryKey: mutation.options.mutationKey,
      })
    },
  }),
})
</code></pre>
<p>这样一来就可以通过定义时的<code>mutationKey</code>去让对应<code>queryKey</code>的数据失效，触发重新执行对应的<code>queryFn</code>请求数据，如果你没有提供<code>mutationKey</code>，它仍然会让所有匹配的活跃查询失效。</p>
<h3>根据 staleTime 排除 Queries</h3>
<p>我经常通过给查询设置<code>staleTime: Infinity</code>来将其标记为“静态”。如果我们不希望这些查询失效，可以设置 <code>queryClient</code>实例的<code>staleTime</code>设置，并通过<code>断言（predicate）</code>过滤器来排除它们。</p>
<pre><code class="language-js">const queryClient = new QueryClient({
  mutationCache: new MutationCache({
    onSuccess: (_data, _variables, _context, mutation) =&gt; {
      const nonStaticQueries = (query: Query) =&gt; {
        // 获取默认的失效时间
        const defaultStaleTime =
          queryClient.getQueryDefaults(query.queryKey).staleTime ?? 0
        // 不同的 useQuery 使用相同的 queryKey 仅产生多个观察者，收集失效时间数组
        const staleTimes = query.observers
          .map((observer) =&gt; observer.options.staleTime)
          .filter((staleTime) =&gt; staleTime !== undefined)
				// 获取失效时间
        const staleTime =
          query.getObserversCount() &gt; 0
            ? Math.min(...staleTimes)
            : defaultStaleTime
				// 判断是否使用默认的失效机制
        return staleTime !== Number.POSITIVE_INFINITY
      }

      queryClient.invalidateQueries({
        queryKey: mutation.options.mutationKey,
        predicate: nonStaticQueries,
      })
    },
  }),
})
</code></pre>
<blockquote>
<p>invalidateQueries 中的 <code>predicate</code>函数将对每一个 <code>query</code>实例进行遍历，并且根据其返回的布尔值决定是否要让其失效。</p>
</blockquote>
<p>确定查询的实际<code>staleTime</code>并不是那么简单，因为<code>staleTime</code>是观察者级别的属性。但这是可行的，我们还可以将断言过滤器与其他过滤器（如<code>queryKey</code>）结合使用。很棒。</p>
<h3>使用 meta 选项</h3>
<p>我们可以使用<code>meta</code>字段来存储关于<code>mutation</code>的元数据（静态信息）。举个例子，我们可以添加一个<code>invalidates</code>字段来标记这个<code>mutation</code>，然后再用这些标记来模糊匹配我们希望失效的查询：</p>
<pre><code class="language-js">import { matchQuery } from &#39;@tanstack/react-query&#39;

const queryClient = new QueryClient({
  mutationCache: new MutationCache({
    onSuccess: (_data, _variables, _context, mutation) =&gt; {
      queryClient.invalidateQueries({
        predicate: (query) =&gt;
          // 一次性让所有匹配的标签失效
          // 或者让所有活跃内容失效（亦或是 false 不让任何缓存失效）
          mutation.meta?.invalidates?.some((queryKey) =&gt;
            matchQuery({ queryKey }, query)
          ) ?? true,
      })
    },
  }),
})

// usage:
useMutation({
  mutationFn: updateLabel,
  meta: {
    invalidates: [[&#39;issues&#39;], [&#39;labels&#39;]],
  },
})
</code></pre>
<p>如上所示，我们通过<code>meta</code>字段让<code>issue</code>和<code>labels</code>匹配的项失效，触发其重新获取数据。</p>
<p>这里，我们仍然使用谓词函数来进行一次调用到<code>queryClient.invalidateQueries</code>。但在函数内部，我们使用<code>matchQuery</code>进行模糊匹配，你可以从 <code>React Query</code> 中导入这个函数。这个函数在将单个<code>queryKey</code>作为过滤器传递时内部也会使用，但现在我们可以用多个键来进行匹配。</p>
<p>这种模式比在<code>useMutation</code>的<code>onSuccess</code>回调函数中调用<code>queryClient.invalidateQueries</code>更好一些，至少我们不用每次都使用<code>useQueryClient</code>来引入<code>queryClient</code>。另外，这种模式还和默认让所有内容失效结合起来，非常灵活。</p>
<blockquote>
<p>在 TypeScript 项目中使用 meta 属性，需要修改其默认类型：</p>
<pre><code class="language-js">declare module &#39;@tanstack/react-query&#39; {
  interface Register {
    mutationMeta: {
      invalidates?: Array&lt;QueryKey&gt;
    }
  }
}
</code></pre>
</blockquote>
<h3>等还是不等？</h3>
<p>在上面的例子中我们几乎等待<code>invalidation</code>结束，如果你希望你的变更（mutation）尽快结束，那么这就够了。</p>
<p>我经常遇到一个具体的场景是希望所有内容都失效，但是却让<code>mutation</code>保持挂起的状态，直到其中若干重要的更新完成。例如，我希望变更标签数据后等待特定标签的<code>refetch</code>完成，但是并不等待其他同样<code>invalidation</code>的数据重新获取完成。</p>
<p>我们可以扩展一下<code>meta</code>对象来实现这个需求，举个例子：</p>
<pre><code class="language-js">useMutation({
  mutationFn: updateLabel,
  meta: {
    invalidates: [[&#39;labels&#39;, &#39;issues&#39;]],
    awaits: [[&#39;labels&#39;]],
  },
})
</code></pre>
<blockquote>
<p>以下内容为译者扩展，原文并未出现如何实现 awaits 控制的代码</p>
</blockquote>
<p>我们可以在配置 <code>queryClient</code>的时候，扩展<code>onSuccess</code>函数，内部检查是否提供<code>awaits</code>数组来控制返回值。</p>
<pre><code class="language-typescript"> mutationCache: new MutationCache({
    onSuccess(data, variables, context, mutation): unknown {
      queryClient.invalidateQueries({
        predicate: (query) =&gt;
          // invalidate all matching tags at once
          // or nothing if no meta is provided
          mutation.meta?.invalidates?.some((queryKey) =&gt;
            matchQuery({ queryKey }, query)
          ) ?? false,
      });
      const awaits = mutation.meta?.awaits;
      if (Array.isArray(awaits)) {
        return queryClient.invalidateQueries(
          { queryKey: awaits },
          { cancelRefetch: false }
        );
      }
    },
  }),
</code></pre>
<p>这里我们如果提供了<code>awaits</code>内容，就将<code>Promise</code>返回，<code>useMutation</code>处定义的<code>onSuccess</code>函数将会在这个<code>Promise</code>转为<code>resolve</code>状态之后才执行，如此一来我们就可以等待指定的<code>query</code>实例失效并且请求新数据之后再执行自己的<code>onSuccess</code>回调函数逻辑了。</p>
<p>回到原文。</p>
<p>或者，我们可以利用 <code>MutationCache</code> 上的回调在 <code>useMutation</code> 上的回调之前运行的机制，配合全局回调设置为使所有内容无效，再添加一个本地回调来等待我们想要的结果：</p>
<pre><code class="language-typescript">const queryClient = new QueryClient({
  mutationCache: new MutationCache({
    onSuccess: () =&gt; {
      queryClient.invalidateQueries()
    },
  }),
})

useMutation({
  mutationFn: updateLabel,
  onSuccess: () =&gt; {
    // returning the Promise to await it
    return queryClient.invalidateQueries(
      { queryKey: [&#39;labels&#39;] },
      { cancelRefetch: false }
    )
  },
})
</code></pre>
<p>这将会：</p>
<ul>
<li>全局配置中让所有匹配的 query 失效，但是在回调中我们既没有等待，也没有返回<code>Promise</code>内容，因此这是一个一劳永逸的失效行为</li>
<li>然后，我们本地提供的 <code>onSuccess</code>回调将会立即执行，我们可以主动返回一个仅让匹配<code>[&#39;labels&#39;]</code>查询<code>key</code>的失效 <code>Promise</code>状态，因此 <code>Mutation</code> 将保持待<code>pending</code>状态，直到重新获取<code>[&#39;labels&#39;]</code> 为止。</li>
</ul>
<blockquote>
<p><strong>cancelRefetch</strong></p>
<p>我们将 cancelRefetch 设置为 false 传递给 invalidateQueries，默认为 true 是因为我们通常希望命令式重新获取调用优先并取消当前正在运行的调用，以保证之后的数据是最新的。</p>
<p>但是在这里，情况刚好相反。由于我们的全局回调已经使所有内容无效，包括我们想要等待的查询，因此我们只需使用无效查询来“拾取”已经在运行的 Promise 并返回它。如果你不这样，那么就会看到一个针对<code>[&#39;labels&#39;]</code>的请求被创建。</p>
</blockquote>
<p>我认为这表明添加您熟悉的自动失效抽象并不需要很多代码。请记住，<code>每个抽象都有成本：它是一个新的 API，需要正确学习、理解和应用。</code></p>
<p>我希望通过展示所有这些可能性，可以更清楚地了解为什么我们没有在 <code>React Query</code> 中内置任何内容。找到一个足够灵活、能够覆盖所有情况而不臃肿的 API 并不是一件容易的事情。为此，我更愿意为您提供在用户空间中构建此工具的工具。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[996 让我身心皆疲]]></title>
            <link>https://i5lin.top/blog/post/2329007295</link>
            <guid isPermaLink="false">2329007295</guid>
            <pubDate>Sat, 01 Jun 2024 08:30:01 GMT</pubDate>
            <description><![CDATA[喵，今天天气预告]]></description>
            <content:encoded><![CDATA[<p><img src="https://pbs.twimg.com/media/GNNlgVjbQAAu3eu?format=jpg&name=large" alt="喵，今天天气预告"></p>
<p>OK！继续来分享上一周接触到了一些讯息！</p>
<h2>Node.js CLI Apps Best Practices</h2>
<p><img src="https://github.com/lirantal/nodejs-cli-apps-best-practices/raw/main/.github/node-js-cli-apps-best-practices.png" alt=""></p>
<p><a href="https://github.com/lirantal/nodejs-cli-apps-best-practices">lirantal/nodejs-cli-apps-best-practices: The largest Node.js CLI Apps best practices list ✨</a>  是一个指导性的仓库，主要介绍了开发<code>Node.js</code>命令行程序的最佳实践，如果你也需要开发命令行程序，非常推荐学习一下。</p>
<h2>速通 TypeScript</h2>
<p><a href="https://github.com/joye61/typescript-tutorial">joye61/typescript-tutorial: TypeScript速成教程（2小时速成）</a>：本教程以<strong>快速工程实践</strong>为目标，旨在让读者<strong>速成</strong>，能够迅速的GET到语言的核心知识点，并付诸实践！</p>
<p>此文最近更新于三年前，初始提交是五年前，其版本内容会缺少最近几年的新知识，不过难得之处在于作者写得非常简洁，值得不擅长<code>TypeScript</code>的开发者花两小时学习一下。</p>
<h2>Fuzzysort</h2>
<p><img src="https://camo.githubusercontent.com/e48f2780f5a126e6d01d109ace0fb70fb0194bf36e8f784da1b3df2e35597832/68747470733a2f2f692e696d6775722e636f6d2f61786b4f4d56732e706e67" alt=""></p>
<p><a href="https://github.com/farzher/fuzzysort">farzher/fuzzysort: Fast SublimeText-like fuzzy search for JavaScript.</a> 如果你需要在<code>JavaScript</code>生态下开发内容模糊搜索的功能，那么<code>fuzzysort</code>将会是一个很好的开源选项。</p>
<p><img src="https://camo.githubusercontent.com/e25c8399f5925447cfe16b162da891b73a07cf60e0f8a1dcce77964ba39c0f77/68747470733a2f2f692e696d6775722e636f6d2f6d7561773336332e676966" alt=""></p>
<p>其优点是：<code>零依赖</code>+<code>单文件</code>+<code>仅 5kb</code>+<code>快速反应</code>，根据作者的统计 13000 个适度大小的文件模糊搜索时间能保持在 1ms 以下。</p>
<h2>Windows Edeg 卸载</h2>
<p>微软并未在软件卸载程序中心提供<code>Edge</code>浏览器的卸载功能，因此我找到了下面这个程序来卸载浏览器：</p>
<p><a href="https://github.com/ShadowWhisperer/Remove-MS-Edge/">ShadowWhisperer/Remove-MS-Edge: Uninstall Microsoft Edge silently, through an executable or batch script.</a> </p>
<p>这是一个 windows edge 浏览器卸载工具，支持仅卸载 <code>edge</code> 的同时保存 <code>WebView</code>，以免某些依赖于<code>WebView</code>的应用出现问题。</p>
<h2>Velja</h2>
<p><a href="https://apps.apple.com/us/app/velja/id1607635845?mt=12">Velja on the Mac App Store</a> 快捷设置 macOS 默认浏览器的软件</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20240531170246090.png" alt="image-20240531170246090"></p>
<h2>Command X</h2>
<p><a href="https://sindresorhus.com/command-x">Command X — Sindre Sorhus</a> macOS 模拟 windows 使用<code>command</code>+<code>x</code>组合键剪切文件</p>
<h2>Quick ChatGPT</h2>
<p><a href="https://sindresorhus.gumroad.com/l/quickgpt">QuickGPT</a> Velja 和 Command X 的作者的另一个免费应用，调用了官方的接口，封装了一个 UI，配合系统快捷键可以快速切换和显示 ChatGPT，很好用。</p>
<h2>Notion 风格的头像生成应用</h2>
<p><a href="https://notion-avatar.vercel.app/zh">Notion 风格头像制作</a></p>
<h2>Pic Smaller</h2>
<p><a href="https://txx.cssrefs.com/">Pic Smaller – Compress JPEG, PNG, WEBP, AVIF, SVG and GIF images intelligently</a> 国人作者开发的一款开源的图片压缩应用，数据都在客户端，压缩操作也不经过服务器，压缩效果可以微调。</p>
<h2>极客时间电子书分享</h2>
<p><a href="https://github.com/it-ebooks-0/geektime-books">it-ebooks-0/geektime-books: :books: 极客时间电子书</a>：一个<code>github</code>上的电子书分享仓库。</p>
<h2>⌘+K</h2>
<p>很多应用都有内部的资源检索功能支持，如果你也需要编写类似的应用，可以考虑：</p>
<p><a href="https://cmdk.paco.me/">Fast, composable, unstyled command menu for React — ⌘K</a> </p>
<p>这是一个无样式组件，提供了强大的内容搜索逻辑控制，我们只需要为它添加自己喜欢的样式就好。</p>
<p>如果你使用<code>React</code>，那么也可以无缝使用：<a href="https://react-cmdk.com/">react-cmdk | Build your dream command palette</a> 这个带默认样式的组件</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20240530202352404.png" alt="image-20240530202352404"></p>
<p>如果你使用<code>vue</code>，那么也有对应的封装库支持：</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20240530202327090.png" alt="image-20240530202327090"></p>
<h2>最后</h2>
<blockquote>
<p>非常期待读者的点赞和支持，或多或少我都能从中得到鼓励❤️</p>
</blockquote>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Vue 3.3 发布：十分钟速递]]></title>
            <link>https://i5lin.top/blog/post/1708449126</link>
            <guid isPermaLink="false">1708449126</guid>
            <pubDate>Sat, 13 May 2023 05:55:47 GMT</pubDate>
            <description><![CDATA[大家好，现在是 2023 年 5 月 13 日上午。不知道大家有没有注意到，前天`Vue`发布了`3.3`版本。我今天才看了一下，新的一些特性非常有用，因此想在这里给还没看过的开发者朋友稍微介绍一下最新的特性和使用案例。

![](https://images.unsplash.com/photo-1653387137517-fbc54d488ed8?ixlib=rb-4.0.3&ixid=M]]></description>
            <content:encoded><![CDATA[<p>大家好，现在是 2023 年 5 月 13 日上午。不知道大家有没有注意到，前天<code>Vue</code>发布了<code>3.3</code>版本。我今天才看了一下，新的一些特性非常有用，因此想在这里给还没看过的开发者朋友稍微介绍一下最新的特性和使用案例。</p>
<p><img src="https://images.unsplash.com/photo-1653387137517-fbc54d488ed8?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" alt=""></p>
<blockquote>
<p>“This release is focused on developer experience improvements” 					- 此版本主要专注开发人员的体验提升</p>
</blockquote>
<h3>总结</h3>
<p>“献给时间紧急的朋友 🚀”</p>
<p>Vue 3.3 已发布，改进了开发人员体验、新语法和宏以及对 <code>TypeScript</code> 的改进。该版本包括通用组件、定义插槽和<code>emit</code>类型、定义组件选项以及响应式<code>props</code>解构和定义模型语法糖等实验性功能。该版本还包括弃用 Reactivity Transform。新宏之一，<code>defineOptions</code>，用于定义 <code>Options API</code> 选项，可用于定义除 props、emits、expose 和 slot 之外的任何选项。另一个新功能是 <code>hoistStatic</code>，它通过将常量声明提升到顶层来优化 <code>SFC</code> 编译器。最后，<code>defineModel</code> 是一个新的语法糖宏，用于定义双向绑定 props。</p>
<h3>单文件组件类型导入</h3>
<p>了解<code>React</code>开发的朋友可能在接触<code>Vue3.2+TypeScript</code>时对于单文件组件无法导入外部 TypeScript 类型而感到困惑和遗憾，其原因在于：</p>
<blockquote>
<p>This is because Vue needs to be able to analyze the properties on the props interface in order to generate corresponding runtime options.</p>
</blockquote>
<p>**Vue 需要能够分析 props 接口上的属性，以便生成相应的运行时选项。**框架底层的限制让开发者只能在定义<code>DefineProps</code>和<code>defineEmits</code>的时候使用本地 TypeScript type 或 interface。</p>
<p>而 <code>3.3</code> 版本终于解决了这个问题（开发团队在 Github issue 里曾承诺会尽快解决这个问题，他们做到了），我们可以从外部导入 TypeScript 类型了：</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot;&gt;
import type { Props } from &#39;./foo&#39;

// imported + intersection type
defineProps&lt;Props &amp; { extraProp?: string }&gt;()
&lt;/script&gt;
</code></pre>
<p>除此之外，还可以从第三方的包导入其 TypeScript 类型或接口，亦或是引用全局类型描述文件&quot;.d.ts&quot;内的类型。例如：</p>
<pre><code class="language-typescript">import type { Props } from &#39;some-package&#39;
defineProps&lt;Props&gt;()
</code></pre>
<p>但需要注意的是，开发者不能在 props 类型中使用条件类型：</p>
<pre><code class="language-typescript">import type { Props } from &#39;./foo&#39;
// will throw a compiler error
defineProps&lt;Props extends object ? Props : {}&gt;()
</code></pre>
<p>究其原因，还是因为这个特性是基于<code>AST</code>解析的，而非<code>TypeScript</code>的支持。</p>
<h3>泛型组件（Generic Component)</h3>
<p>直接看代码：</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot; generic=&quot;T&quot;&gt;
defineProps&lt;{
  items: T[]
  selected: T
}&gt;()
&lt;/script&gt;
</code></pre>
<p>亦或是在泛型定义的时候继承内置或从第三方导入的类型：</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot; generic=&quot;T extends MyItem&quot;&gt;
  interface MyItem {
    name: string;
    foo: number
  }

  defineProps&lt;{
    list: T[],
    modelValue?: T
  }&gt;
&lt;/script&gt;
</code></pre>
<p>使用泛型组件时，要注意更新<code>volar</code>和<code>vue-tsc</code>，在调用组件时传参将会变得更加灵活。</p>
<p>诚然，这个泛型组件笔者没有写过，对此也不够了解，十分抱歉。如果你对此很有兴趣，推荐看看讨论区：<a href="https://github.com/vuejs/rfcs/discussions/436"><a href="https://github.com/vuejs/rfcs/discussions/436">Generic component enhancements - Discussion #436</a></a></p>
<h3>更优雅的 DefineEmits</h3>
<p>此前，TypeScript 和 Vue 结合定义 <code>DefineEmits</code>代码只支持如下调用签名的语法：</p>
<pre><code class="language-typescript">&lt;script setup lang=&quot;ts&quot;&gt;
const emits = defineEmits&lt;{
  (evt: &#39;update:modelValue&#39;, value: string): void
  (evt: &#39;change&#39;): void
}&gt;()
</code></pre>
<p>笔者的体验或许跟大多数人一样，这样的写法有点冗长。在<code>3.3</code>版本我们可以这样写了：</p>
<pre><code class="language-typescript">// ⬇️ Vue 3.3 之后
const emits = defineEmits&lt;{
  &#39;update:modelValue&#39;: [value: string],
  &#39;change&#39;: []
}&gt;()
&lt;/script&gt;
</code></pre>
<p>在类型定义中，键就是事件名，值就是函数参数。<strong>新写法不是必须的，你依然可以使用旧的写法</strong>。</p>
<p>笔者较懒，推荐后者 😂</p>
<h3>DefineSlots</h3>
<blockquote>
<p>这是一个全新的特性 👏🏻</p>
</blockquote>
<p>这个特性可以让我们显式地定义插槽的类型，这个特性对于复杂的组件较为有用，来看看下面这个配合泛型组件的示例（代码源于参考文章）：</p>
<p>分页组件：</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot; generic=&quot;T&quot;&gt;
// 子组件 Paginator
defineProps&lt;{
  data: T[]
}&gt;()

defineSlots&lt;{
  default(props: { item: T }): any
}&gt;()
&lt;/script&gt;
</code></pre>
<p>使用：</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;!-- 父组件 --&gt;
  &lt;Paginator :data=&quot;[1, 2, 3]&quot;&gt;
    &lt;template #default=&quot;{ item }&quot;&gt;{{ item }}&lt;/template&gt;
  &lt;/Paginator&gt;
&lt;/template&gt;
</code></pre>
<p>显式定义类型优于隐式定义类型，有助于提高代码的可读性。</p>
<h3>实验特性</h3>
<blockquote>
<p>试验性的特性都需要在编译工具的配置中开启试验性属性。</p>
</blockquote>
<h4>Props 响应式解构</h4>
<p>此特性可以让我们少写一些代码：</p>
<p>自动保持响应式解构，并且可以使用非常符合 JavaScript 的语法的方式设置默认值。</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { watchEffect } from &#39;vue&#39;

const { msg = &#39;hello&#39; } = defineProps([&#39;msg&#39;])

watchEffect(() =&gt; {
  // accessing `msg` in watchers and computed getters
  // tracks it as a dependency, just like accessing `props.msg`
  console.log(`msg is: ${msg}`)
})
&lt;/script&gt;

&lt;template&gt;{{ msg }}&lt;/template&gt;
</code></pre>
<p>如果你想用 TypeScript，则：</p>
<pre><code class="language-typescript">const { count } = defineProps&lt;{ count: 0 }&gt;()
</code></pre>
<p>此外，记住开启试验性配置：</p>
<p>笔者使用<code>vite</code>编译项目：</p>
<pre><code class="language-js">// vite.config.js
export default {
  plugins: [
    vue({
      script: {
        propsDestructure: true
      }
    })
  ]
}
</code></pre>
<h4>defineModel</h4>
<p>此前，编写支持<code>v-model</code>组件时需要声明<code>props</code>和<code>emit</code>，代码如下所示：</p>
<pre><code class="language-vue">&lt;!-- BEFORE --&gt;
&lt;script setup&gt;
const props = defineProps([&#39;modelValue&#39;])
const emit = defineEmits([&#39;update:modelValue&#39;])
console.log(props.modelValue)

function onInput(e) {
  emit(&#39;update:modelValue&#39;, e.target.value)
}
&lt;/script&gt;

&lt;template&gt;
  &lt;input :value=&quot;modelValue&quot; @input=&quot;onInput&quot; /&gt;
&lt;/template&gt;
</code></pre>
<p>现在，开启这个<code>defineModel</code>实验性属性就可以这样写：</p>
<pre><code class="language-vue">&lt;!-- AFTER --&gt;
&lt;script setup&gt;
const modelValue = defineModel()
console.log(modelValue.value)
&lt;/script&gt;

&lt;template&gt;
  &lt;input v-model=&quot;modelValue&quot; /&gt;
&lt;/template&gt;
</code></pre>
<p>显著降低代码量，懒人必备，早点下班回家撸猫才是正经。</p>
<h3>DefineOptions 宏定义</h3>
<p>笔者在写缓存组件的时候，发现要定义组件的名字，必须在<code>&lt;script setup&gt;</code>之外再使用<code>&lt;script&gt;</code>来声明组件名（我也不需要使用默认的文件名）。实在是麻烦，于是搜索到了 **Kevin Deng **的<a href="https://www.npmjs.com/package/unplugin-vue-define-options"><a href="https://www.npmjs.com/package/unplugin-vue-define-options">unplugin-vue-define-options - npm</a></a> 插件来实现在<code>setup</code>里面定义组件名。</p>
<p>现在，感谢作者的贡献，我们终于可以在<code>3.3</code>版本直接使用内置功能了：</p>
<pre><code class="language-vue">&lt;script setup&gt;
defineOptions({
  name: &#39;Foo&#39;,
  inheritAttrs: false,
  // ... 更多自定义属性
})
&lt;/script&gt;
</code></pre>
<h3>基础设施完善</h3>
<p>除了以上更新之外，开发团队还更新了一些关于基础设施的内容：</p>
<ul>
<li><p>提升静态常量，这个属于编译器优化</p>
</li>
<li><p>提高构建速度，使用<code>rollup-plugin-esbuild</code></p>
</li>
<li><p>提高测试速度：从<code>Jest</code>到<code>Vitest</code>（OK，大家转到<code>Vitest</code>来吧）</p>
</li>
<li><p>加速类型生成，使用<code>rollup-plugin-dts</code></p>
</li>
<li><p>增加回归测试</p>
</li>
</ul>
<h3>参考文档</h3>
<ul>
<li><a href="https://blog.vuejs.org/posts/vue-3-3"><a href="https://blog.vuejs.org/posts/vue-3-3">Announcing Vue 3.3 | The Vue Point</a></a></li>
<li>[<a href="https://xlog.sxzz.moe/vue-3-3">Vue 3.3 主要新特性详解 - 三咲智子 Kevin Deng</a>](</li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[介绍我的新标签页插件：Erin]]></title>
            <link>https://i5lin.top/blog/post/1829450237</link>
            <guid isPermaLink="false">1829450237</guid>
            <pubDate>Mon, 31 Jul 2023 15:20:04 GMT</pubDate>
            <description><![CDATA[![](https://lh3.googleusercontent.com/LcAQ86qgz4NsdEhFLWzFZBmz4VPDyGiNRoVpEn3qA6o6iysSUuuztMrSwTJIcjHEyv-3PPGzZnoADm5o4oSIsOTzdw=w128-h128-e365-rj-sc0x00ffffff)

今天，我想介绍一下我的 chrome 浏览器插件：Erin ！

<]]></description>
            <content:encoded><![CDATA[<p><img src="https://lh3.googleusercontent.com/LcAQ86qgz4NsdEhFLWzFZBmz4VPDyGiNRoVpEn3qA6o6iysSUuuztMrSwTJIcjHEyv-3PPGzZnoADm5o4oSIsOTzdw=w128-h128-e365-rj-sc0x00ffffff" alt=""></p>
<p>今天，我想介绍一下我的 chrome 浏览器插件：Erin ！</p>
<img width="1398" alt="image" src="https://github.com/Developer27149/blog/assets/23721611/a64823ca-cca2-4680-9988-2733e6764d7f"><h3>前言</h3>
<p>很久以前，我发现每次打开新的浏览器标签页都会跳出一个导航网站，上面布满了各种各样毫无意义的“新闻、娱乐信息”，甚至慢慢地出现一些“一刀 999”的广告。</p>
<p>于是，我开始想办法清理掉这些东西。某一天，我发现朋友在用<code>Infinity</code>这个浏览器插件，界面非常干净。于是，我便果断安装了这个插件。</p>
<p>又过了一段时间，我开始了解到 chrome 浏览器扩展开发的知识，于是便开始想着编写自己的新标签页。</p>
<p>于是，我发布了最早的标签页插件：<a href="https://chrome.google.com/webstore/detail/%E5%B2%9A/pgbomeeopmblmenapfgbhjobohpnegil">岚</a>！尽管没有用户，并且功能非常简陋，代码也只是勉强达到<code>能跑</code>的程度而已。无论如何，那是我第一个上架的插件。后续我又重写了这个插件，但是以另一个名字发布&quot;<code>linlan</code>&quot;。令我没想到的是，这个插件居然有一个国外的用户给了五颗星😂，还留言了：</p>
<p><img src="https://cdn.jsdelivr.net/gh/Developer27149/uPic@main/uPic/image-20230731230224227.png" alt="image-20230731230224227"></p>
<h3>Erin</h3>
<p>在体验过一段时间<code>Plasmo</code>这个浏览器扩展开发框架之后，我决定花点时间使用<code>React</code>重新开发新的 chrome 标签页插件：<a href="https://chrome.google.com/webstore/detail/erin/nobdiagkeelcndaijbdninbmjkdahggd">Erin</a>!</p>
<blockquote>
<p>做一个纯净的新标签页插件！</p>
</blockquote>
<p>其保留了之前的新标签页功能，例如：</p>
<ul>
<li>4k 壁纸展示和选择、下载、自定义上传、收藏等</li>
<li>搜索引擎切换和搜索建议</li>
<li>书签 Dock</li>
<li>当前时间 tips</li>
</ul>
<p>此外，还有朋友提出了新的需求：</p>
<ul>
<li>tabs 页面管理：方便查看所有窗口的页面以及不同页面的打开父子关系、批量操作等（很<code>开发者</code>的需求）</li>
</ul>
<p>此外，我还想支持：</p>
<ul>
<li>便签模式：展示日常记录便签，用后即删，支持<code>markdown</code>语法</li>
<li>copy as markdown </li>
<li>video for Picture in Picture (播放中的视频快速启用画中画模式)</li>
<li>图片自定义格式下载</li>
<li>稍后阅读记录（按标签）</li>
</ul>
<p>于是，我一步步加上了这些功能，并且发布上架到了 chrome 插件商店，并且还添加了一个用于介绍<code>Erin</code>的页面（需要一些魔法才能打开）：<a href="https://erin-homepage.vercel.app/">Erin - 极简的 chrome 新标签页插件</a></p>
<h3>最后</h3>
<p>这个插件我日常会用，后续或许会有一些新的功能加进来，亦或是增加用户系统等等。</p>
<p><strong>BTW</strong>，欢迎大家试用和提出建议~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[MacOS 翻译软件 - 最终的选择]]></title>
            <link>https://i5lin.top/blog/post/1599633937</link>
            <guid isPermaLink="false">1599633937</guid>
            <pubDate>Sat, 25 Feb 2023 09:37:03 GMT</pubDate>
            <description><![CDATA[我用`macOS`苹果系统已经很久了，最开始要算到大四那会折腾了自己的宏碁笔记本装黑苹果，再到后来自己组装`ITX`小主机来装黑苹果。不过最后还是回归白苹果，在很长一段时间内我都有翻译的需求（自己英语不好），并且尝试过好几种方案，比如：词典、系统自带的翻译功能、浏览器翻译插件、浏览器自带的网页翻译功能甚至是独立的翻译软件。

![](https://images.unsplash.com/ph]]></description>
            <content:encoded><![CDATA[<p>我用<code>macOS</code>苹果系统已经很久了，最开始要算到大四那会折腾了自己的宏碁笔记本装黑苹果，再到后来自己组装<code>ITX</code>小主机来装黑苹果。不过最后还是回归白苹果，在很长一段时间内我都有翻译的需求（自己英语不好），并且尝试过好几种方案，比如：词典、系统自带的翻译功能、浏览器翻译插件、浏览器自带的网页翻译功能甚至是独立的翻译软件。</p>
<p><img src="https://images.unsplash.com/photo-1534617619381-fb71fe6471fb?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2934&q=80" alt=""></p>
<p>我看到有些朋友也有翻译的需求，因此我想或许我可以分享一下自己的翻译方案。</p>
<p>总的来说，我最喜欢的方案是：独立翻译软件“Bob”！</p>
<img src="https://bobtranslate.com/images/hero.png" style="zoom:5%;" width="64" /><p>没错，就是上面这个应用。我猜这个应用图标应该跟作者本人的长相有一定的关系😂，开个玩笑~</p>
<p>今天，我来分享给大家我是如何使用这个翻译软件的。</p>
<h4>Bob简介</h4>
<p><a href="https://bobtranslate.com/">Bob</a>：一款<code>macOS</code>平台的翻译软件，支持<code>OCR</code>光学字符识别和强大的插件系统。其界面简洁，结构紧凑，绝对是一款经典的小而美的软件。</p>
<p>其支持的功能如下所示：</p>
<ul>
<li>划词翻译</li>
<li>截图翻译</li>
<li>输入翻译</li>
</ul>
<p>这些功能涉及的翻译基本都需要第三方API来提供支持，内置的翻译功能比较弱，其官方文档非常简明且精炼，用户只需要照着文档说明就可以轻松配置翻译功能。</p>
<h4>使用建议</h4>
<p><code>Bob</code>支持配置一系列的自定义快捷键来使用其功能，例如我会选择自己常用的<code>command+7</code>来支持选中文本后的翻译功能，亦或是<code>OCR</code>截图识别翻译，并且支持配置历史记录和窗口位置和状态。这样一来，可以非常方便的随时切换翻译窗口。</p>
<p>此外，我觉得<code>Bob</code>最好用的还是要属插件功能。社区里有非常多的翻译插件，开发者甚至可以定义自己的翻译插件来做自己想做的任何事情。</p>
<p>举个例子，在翻译的同时记录用户的未知词汇，后期进行统计和重复记忆练习等。</p>
<h4>推荐插件</h4>
<p>我安装了三个非常好用的插件，首先就是：<a href="https://github.com/tingv/bobplugin-google-translate">tingv/bobplugin-google-translate: Bob 的 Google 翻译插件，无需申请 API 秘钥</a>， 下载安装之后即可在服务设置里启用此插件，只需要配置代理域名为：<code>translate.google.com</code>即可在没有谷歌API的时候使用谷歌翻译功能。</p>
<p>其次，我想推荐另一个翻译插件：<a href="https://github.com/clubxdev/bob-plugin-deeplx">clubxdev/bob-plugin-deeplx: Bob DeepL 插件</a>!</p>
<blockquote>
<p><code>DeepL</code>是世界上最大的人工翻译数据集。 翻译效果相比国内某些翻译应用来说，部分句子翻译结果可能更容易理解😂，不会过于生硬。虽然这只是一点点的用户体验提升，但是何乐而不为呢？</p>
</blockquote>
<p>下载安装此插件之后，还需要使用配合<a href="https://hub.docker.com/r/kanikig/deepl-bk">kanikig/deepl-bk - Docker Image | Docker Hub</a>和<code>Docker</code>一起使用，操作如下：</p>
<pre><code class="language-bash">docker pull kanikig/deepl-bk
docker run --platform linux/amd64 -p 8282:80 -itd kanikig/deepl-bk
</code></pre>
<p>上述命令首先是拉去镜像服务，然后指定平台运行此镜像（我的笔记本是苹果芯片，因此需要指定平台）。</p>
<p>此时，<code>docker</code>将会运行这个镜像并且映射其端口到本地的8282端口，你甚至可以稍微测试一下：</p>
<pre><code class="language-bash">curl &#39;127.0.0.1:8282/translat&#39; -X POST -d &#39;{&quot;source_lang&quot;:&quot;EN&quot;,&quot;text&quot;:&quot;Hello World&quot;,&quot;target_lang&quot;:&quot;ZH&quot;}&#39;
</code></pre>
<p>上面的 <code>curl</code>命令正常情况下即可得到<code>DeepL</code>翻译的本地返回数据。</p>
<blockquote>
<p>如果你不想要Docker，也可以在这里下载一分<a href="https://github.com/reycn/deepl-custom-server-for-bob">reycn/deepl-custom-server-for-bob</a>Python源代码本地或在VPS上部署。</p>
</blockquote>
<p><img src="https://user-images.githubusercontent.com/23721611/221350099-5aa30ab2-f134-4acb-89e5-7f4a62799a2f.png" alt="image"></p>
<p>打开<code>Bob</code>设置页面选择服务下的<code>DeepL X</code>插件配置本地的接口域名即可保存。</p>
<p>最后，便是蹭一下<code>OpenAI</code>的风：</p>
<p><a href="https://github.com/yetone/bob-plugin-openai-translator">yetone/bob-plugin-openai-translator: 基于 OpenAI API (GPT-3) 的 Bob 翻译插件，让我们一起迎接不需要巴别塔的新时代！</a></p>
<p>安装此插件之后稍微有点麻烦的是从<a href="https://platform.openai.com/account/api-keys">OpenAI</a>获取一个<code>API KEY</code>来使用此功能。</p>
<p>如果你有一个<code>ChatGPT</code>账户并且能够自由的获取互联网信息，那么你可以轻松前往上面的链接去拿到一个<code>KEY</code>。</p>
<p>如果没有，其实也可以去这个世界上最大的程序员分享网站<code>Github</code>去搜索<code>OPEN_AI_KEY</code>的代码段，很多人将其开源了出来你随便找一个就行。</p>
<p>当然，<code>OPEN_AI_KEY</code>换成<code>YOUDAO_SECRET</code>之类的名字也可以拿到国内服务商的一些<code>KEY</code>。</p>
<p><code>OpenAI</code>翻译稍微慢一点，但是人工智能的加持在某些时候真的很棒，一些中英文组合的内容也可以翻译得非常好 。哎，<code>AI</code>革命真的来了。</p>
<h4>最后</h4>
<p>为了照顾<code>Windows</code>用户并且秉持坚持选择开源作品的原则，这里推荐一款实用性非常高的翻译软件：<a href="https://github.com/zu1k/translator">zu1k/translator: 简单、轻量、好用的划词翻译软件</a>!</p>
<blockquote>
<p>为什么优先选择开源作品？因为公司要赚钱，国内公司更需要赚钱，有什么事是他们做不出来的呢？</p>
</blockquote>
<p>OK 今天的分享就到这里结束了，下次见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[『妙才周刊』在这像X一样的人生中，我们必须扔掉一些东西，总是要有需要照看的东西，你还有时间。]]></title>
            <link>https://i5lin.top/blog/post/1592234147</link>
            <guid isPermaLink="false">1592234147</guid>
            <pubDate>Mon, 20 Feb 2023 17:26:08 GMT</pubDate>
            <description><![CDATA[在这像X一样的人生中，我们必须扔掉一些东西，总是要有需要照看的东西，你还有时间。

But ease donot enter into grown-up life.

-《The weatherman》

![](https://plus.unsplash.com/premium_photo-1666900049633-358025dd45ec?ixlib=rb-4.0.3&ixid=]]></description>
            <content:encoded><![CDATA[<p>在这像X一样的人生中，我们必须扔掉一些东西，总是要有需要照看的东西，你还有时间。</p>
<p>But ease donot enter into grown-up life.</p>
<p>-《The weatherman》</p>
<p><img src="https://plus.unsplash.com/premium_photo-1666900049633-358025dd45ec?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2371&q=80" alt=""></p>
<p>Hello， 大家好。这里是妙才的周刊第27期，本周要分享其他的内容如下：</p>
<h3>易书</h3>
<p><a href="https://search.yibook.org/">易书 电子书聚合搜索引擎</a>：免费+资源丰富+无广告的电子书聚合搜索站。</p>
<h3>Language Reactor</h3>
<p><a href="https://localsend.org/">LocalSend</a>：百万用户量的油管Netflix双语字幕Chrome浏览器插件，如果你想学习英语或者辅助理解英语音频内容，这个插件非常好用。</p>
<h3>Zsh-autosuggestions</h3>
<p><a href="https://github.com/zsh-users/zsh-autosuggestions">zsh-users/zsh-autosuggestions: Fish-like autosuggestions for zsh</a>: 像用<code>fish shell</code>一样用<code>zsh shell</code>，这个插件提供良好的搜索建议补全功能，非常适合日常大量使用终端的朋友。</p>
<h3>Mac App Collection</h3>
<p><a href="https://renegadevi.gitlab.io/mac-app-collection/">Mac App Collection</a> 是一个无广告的<code>MacOS</code>系统精品软件分享列表，就像作者所说的一样：</p>
<blockquote>
<p>The software ecosystem for macOS over the last decade has been growing rapidly. There&#39;s a lot of new software which offers high quality, native and polished experiences, or just enhances the existing macOS experience to suit personal needs, which is often overlooked by what&#39;s being advertised.</p>
<p>Over time, with the amount of advertising, requirements, and bloatware found on store fronts, it&#39;s eventually become harder to browse for good native apps, especially those by smaller indie or hobby developers.</p>
</blockquote>
<p>在过去十年间，MacOS的软件生态发展迅速，有很多优秀的软件被巨大的广告系统、臃肿的应用所淹没，优秀的本地应用程序开发变得越来越难以传播，尤其是小型独立开发者或业余开发者来说，这个站点起源于一个“优秀的软件列表”，并且没有任何商业用途。</p>
<p>如果你是MacOS的用户，非常推荐看看这个列表下的精品应用。</p>
<h3>Teach Anything</h3>
<p><a href="https://www.teach-anything.com/">Teach Anything</a>是一个小而美的Web应用，你可以提出你的问题，然后点击获取答案，应用将会通过对接第三方数据接口获取一个回答，有兴趣可以体验体验~</p>
<h3>Charts.css</h3>
<p><a href="https://chartscss.org/">Charts.css</a>: 让人眼前一亮的纯<code>CSS</code>表格生成库，轻度使用非常方便，能提供一定的定制功能。</p>
<h3>Omnivore</h3>
<p><a href="https://omnivore.app/home">Home - Omnivore</a>是一个跨平台的内容暂存工具，我初步用来保存后续阅读的一些资料或讯息，支持安装Chrome扩展和移动端应用，总体上来说还行。</p>
<h3>Free UI Design Tips EBook</h3>
<p><a href="https://hype4academy.gumroad.com/l/designtips">UI Design Tips (FREE eBook) (ENG)</a> 这是一本英文的免费UI设计电子书，作者是一个从业二十年经验的老设计师，最近为了推广他自己的其他课程，将这本电子书分享了出来，如果你想了解一些设计的讯息和技术，扩充自己的视野，或许可以下载来看看。</p>
<h3>TO-D 杂志</h3>
<p><a href="https://2d2d.io/">TO-D 杂志</a>：<strong>《TO-D 杂志》</strong> 是一个<strong>专注于探讨全球 To-D 领域相关产品、创业公司、融资等资讯的开源独立杂志</strong>。</p>
<blockquote>
<p>To-D 即 To Developers，主要指面向开发者市场的创业活动。但 To-D 并非一个独立的领域划分，它通常和 To-C, To-B 可能存在重叠。不管最终产生付费行为的是开发者个人，还是企业，只要其主要目标使用人群为 Developers 都算 To-D 产品。大家熟知的 Jetbrains, Postman, GitLab 等都属于 To-D 产品。</p>
</blockquote>
<p>此杂志此前仅在字节内网发布，后来于2022年3月才公开发布，有兴趣的开发者或产品人员可以关注一下~</p>
<h3>TZF</h3>
<p><a href="https://github.com/ringsaturn/tzf">ringsaturn/tzf: Get timezone via longitude&amp;latitude in Go in a fast way</a>： 号称最快的Go语言时区查询工具。</p>
<h3>钛盘</h3>
<p><a href="https://tmp.link/">钛盘 - 超好用的文件中转站</a>，不限空间不限速的免费网盘问卷中转站点。</p>
<h3>最后</h3>
<p>好了，今天的分享就先到这里，大家下周见~</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[2023年 - 年初思索]]></title>
            <link>https://i5lin.top/blog/post/1552216667</link>
            <guid isPermaLink="false">1552216667</guid>
            <pubDate>Sun, 22 Jan 2023 17:47:46 GMT</pubDate>
            <description><![CDATA[2023年01月23日00:44:04  室外温度：8° 深夜，肚子饿。

![](https://images.unsplash.com/photo-1467810563316-b5476525c0f9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8aGFwcHklMjBuZXclMjB5ZWFyfGVufDB8MHwwfHw%3D&auto]]></description>
            <content:encoded><![CDATA[<p>2023年01月23日00:44:04  室外温度：8° 深夜，肚子饿。</p>
<p><img src="https://images.unsplash.com/photo-1467810563316-b5476525c0f9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8aGFwcHklMjBuZXclMjB5ZWFyfGVufDB8MHwwfHw%3D&auto=format&fit=crop&w=800&q=60" alt=""></p>
<p>回顾去年的工作，996不可避免，精神内耗永无止境。</p>
<p>但是，收获大于消耗。</p>
<p>去年年后去了福州食亨，入职后真正体会到前端开发在做什么，以及web开发的流程和团队是怎么配合的。</p>
<p>在那段时间很快就写了比前年在深圳一年还多的代码，身边的同事都很不错。感谢当时帮我很多的宇哥，但是不得不说塔斯汀微信小程序简直乱成一锅粥（sorry，宇哥，但那部分代码真的一锅粥）</p>
<p>在福州结识了几个朋友也不错。</p>
<p>年中，投奔了老胡。</p>
<p>新的团队从零开始，我开始负责更多工作，除了日常的编码之外，甚至还有设计（当然我的设计水平简直不忍直视，幸运的是我们后来来了一个超级酷的设计师，括弧：不是设计水平很酷），甚至还负责给新人前端做一面。</p>
<p>这部分工作内容很有趣，对我来说是一种全新的体验。从某天晚上开始，我陆陆续续看简历，语音沟通面试内容，从说话莫名其妙到稍微有一点逻辑，很有趣。</p>
<p>给新人看简历、做一面让我了解了如何处理简历、如何跟面试官沟通，甚至是公司和面试官期望的面试者需要具有什么特性和能力。</p>
<p>工作的部分，大家都是年龄相近的同事，沟通非常顺畅。</p>
<p>对于我带的几个新人，我很抱歉。</p>
<p>如果你们看到我写的这部分内容，我很抱歉。这是我第一次带人，距离我被带仅仅有一个月时间左右😂。</p>
<p>我带得不好，源于自己并没有足够的能量和能力。这也就导致了我们初期不断的加班和返工，我们疲于奔命去处理一些问题。</p>
<p>sorry. 为深夜两点我们还在公司而道歉。</p>
<p>这里可能真的不适合你们仨，新人进入职场有一个经验丰富且足够优秀的前辈太重要了。</p>
<p>最终你们都离开了，这是我的遗憾。</p>
<p>但是，你们几个也不要放松，自己也要好好加油~ </p>
<p>剑伟不要笑嘻嘻的！如琦和迎霜要多巩固基础。（我不好意思发给你们，看得到看不到全靠缘分😂</p>
<p>此外，新团队的leader真的很拼，当然也很好说话。</p>
<p>现在立马发一张好人卡给我组长昭鹏，你真是个好人，但是除夕夜还跟我说工作真的很 fuck-off。</p>
<p>剑鑫身体素质真好，中了新冠还能肝到半夜。</p>
<p>老胡搭建了公司的整个技术体系框架，cool.</p>
<p>技术上，我还有很多需要学习的地方，非常非常多。由于不够了解技术，经常跟老胡讨论问题却答不出结果，尬。</p>
<p>总体来说，工作上去年求仁得仁，唯一不如意的地方就是加班加班加班，我有很严重的精神内耗，非常非常严重，也就导致了熬夜太多，生活作息紊乱，精神内耗也因此加剧，无限循环。</p>
<p>犹如一个孤岛。</p>
<p>2023年01月23日01:38:16</p>
<p>新的一年，祝有所改变。</p>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[谈谈JWT(Json web token) 技术]]></title>
            <link>https://i5lin.top/blog/post/1397548261</link>
            <guid isPermaLink="false">1397548261</guid>
            <pubDate>Wed, 05 Oct 2022 10:03:30 GMT</pubDate>
            <description><![CDATA[今天记录一下学习到的后端接口认证的知识，JWT 技术。
![](https://images.unsplash.com/photo-1515879218367-8466d910aaa4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTB8fGNvZGV8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60)
# 前]]></description>
            <content:encoded><![CDATA[<p>今天记录一下学习到的后端接口认证的知识，JWT 技术。
<img src="https://images.unsplash.com/photo-1515879218367-8466d910aaa4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTB8fGNvZGV8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60" alt=""></p>
<h1>前言</h1>
<p>我的小项目需要使用一种接口认证机制，为了尽量减小服务器端的资源压力，我选择了近年来非常火热的<code>JWT</code>技术。</p>
<h1>简介</h1>
<p>什么是<code>JWT</code>?</p>
<blockquote>
<p><a href="https://jwt.io/">JSON Web Tokens - jwt.io</a></p>
</blockquote>
<p>在其官网的首页上有一段非常显眼的介绍：“JSON Web Token 是一个开放的行业标准（RFC 7519），可以在两方之间安全传输信息”。</p>
<p>如下是一个经过编码的<code>token</code>：</p>
<pre><code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
</code></pre>
<p>请忽视换行符，<code>token</code>由三个部分组成并且使用<code>.</code>号分隔：</p>
<ol>
<li>头部</li>
<li>载荷对象</li>
<li>验证签名</li>
</ol>
<p>让我们通过一张图片来理解这个过程：</p>
<p>![image-20211101223843693](/Users/yi/Library/Application Support/typora-user-images/image-20211101223843693.png)</p>
<p>上述例子中的：<code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9</code>就是头部数据经过<code>base64</code>编码后的结果。<code>eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ</code>则是载体对象经过<code>base64</code>编码后的结果，而<code>SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c</code>则是通过某种算法对<code>头部 base64 编码</code>+<code>.</code>+<code>载荷对象 base64 编码</code>和秘钥进行加密后的结果。</p>
<h2>JWT 的优缺点</h2>
<h3>优点</h3>
<ul>
<li><p>JWT 具有较好的扩展性，且相对于传统的 Session 方案有不占服务器资源的优势</p>
</li>
<li><p>JWT 的载荷对象中可以存储信息（加密或不加密可视情况而定）</p>
</li>
</ul>
<h3>缺点</h3>
<ul>
<li>易滥用：当载荷对象中存储的信息过多的时候，<code>token</code>将会变得非常冗长，所以需要开发者进行控制，避免滥用于数据交换</li>
<li>载荷和头部对象可以直接通过 base64 进行解码得到明文信息（当然开发者可以进行额外的加密控制，或者使用 https 技术防止 token 泄露）</li>
<li>不支持撤销（可以通过增加服务器端缓存或白名单机制来弥补，但同时也增加了资源消耗）</li>
</ul>
<h2>适用场景</h2>
<ul>
<li>安全要求不高的认证场景</li>
<li>短期或一次性认证需求</li>
</ul>
<h1>Koa 和 Jwt</h1>
<p>我的个人项目使用了<code>Koa</code>框架开发后端接口应用，为了使用<code>JWT</code>的特性我需要安装以下两个包：</p>
<ul>
<li>koa-jwt</li>
<li>jsonwebtoken</li>
</ul>
<p><code>koa-jwt</code>中间件让我们能够设置使用<code>jwt</code>来保护哪些路由，并且将请求头中的<code>token</code>数据解析出来，扩展到<code>ctx</code>上下文中。</p>
<p>显然，我们可以将注册和登录的接口作为白名单，直接提供给所有用户使用，当用户注册或登录成功的时候，服务端通过<code>jsonwebtoken</code>创建一个<code>token</code>并返回给客户端。</p>
<p>此时，客户端可以将之保存在<code>localstorage</code>中，并且使用<code>axios</code>之类的库对请求进行拦截，设置拦截器将<code>token</code>放在请求头上面，形如：</p>
<pre><code class="language-bash">Authorization: Bearer &lt;token&gt;	
</code></pre>
<p>此时，<code>token</code>保存在客户端上，服务端可以通过秘钥和加密算法就能解析到<code>token</code>的信息。</p>
<blockquote>
<p>Koa-jwt 和 jsonwebtoken 的使用方法建议阅读文档。</p>
</blockquote>
<p>网上有一种<code>refresh token</code>的说法，即设置一个访问周期，当用户持续访问服务器时根据访问周期来提供新<code>token</code>来替换旧<code>token</code>，从而让保持有效期内访问的用户延长需要重新登录的时间。</p>
<p>显然这种机制需要设置一个合适的<code>token</code>过期时间和刷新周期，被替换的<code>token</code>在某些时间段依然可以访问服务器。</p>
<p>在不同的场合下选择不同的技术才是解决问题的关键！</p>
<h1>参考</h1>
<ul>
<li><a href="https://jwt.io/">JSON Web Tokens - jwt.io</a></li>
<li><a href="https://jwt.io/introduction">JSON Web Token Introduction - jwt.io</a></li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
        <item>
            <title><![CDATA[Babel 7: polyfill方案浅解 ]]></title>
            <link>https://i5lin.top/blog/post/1397543780</link>
            <guid isPermaLink="false">1397543780</guid>
            <pubDate>Wed, 05 Oct 2022 09:59:52 GMT</pubDate>
            <description><![CDATA[前端工程化中，使用 babel 对新语法和 API 预置 polyfill 是非常流行的做法之一，但之前笔者对这个过程和其中使用到的插件不够了解，因此在网上搜寻了一些博客和知识分享，学习一番，再次总结一下。

## 前言

本文将对`babel7`的语法转换和`polyfill`的相关知识进行分析，主要内容为`@babel/preset-env`和`plugin-transform-run]]></description>
            <content:encoded><![CDATA[<p>前端工程化中，使用 babel 对新语法和 API 预置 polyfill 是非常流行的做法之一，但之前笔者对这个过程和其中使用到的插件不够了解，因此在网上搜寻了一些博客和知识分享，学习一番，再次总结一下。</p>
<h2>前言</h2>
<p>本文将对<code>babel7</code>的语法转换和<code>polyfill</code>的相关知识进行分析，主要内容为<code>@babel/preset-env</code>和<code>plugin-transform-runtime</code>，Babel 7.4 之后不再推荐使用<code>@babel/polyfill</code>。</p>
<h2>Preset-env</h2>
<blockquote>
<p>Preset-env 会污染全局环境。</p>
</blockquote>
<p>babel 7 版本推荐使用<code>@babel/preset-env</code>代替以往的诸多<code>polyfill</code>方案，现今我们可以使用<code>preset-env</code>简单地实现语法和功能特性的需求。</p>
<blockquote>
<p>@babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s).</p>
</blockquote>
<p>可知，<code>preset-env</code>可以转换新语法，甚至可以配置转换新的<code>API</code>，通过配置其可选项来实现功能支持。</p>
<p><code>@babel/preset-env</code>有三个常用的关键可选项:</p>
<ul>
<li><p>targets</p>
</li>
<li><p>useBuiltIns</p>
</li>
<li><p>corejs</p>
</li>
</ul>
<h3>target</h3>
<p>首先是<code>targets</code>，这个字段可以填写<code>browserslist</code>的查询字符串，官方推荐使用<code>.browserslistrc</code>文件去指明编译的<code>target</code>，这个配置文件还可以和<code>autoprefixer</code>、<code>stylelint</code>等工具一起共享配置。</p>
<p>所以某种程度上不推荐在<code>.babelrc</code>的<code>preset-env</code>配置中直接使用<code>targets</code>进行配置。</p>
<blockquote>
<p>如果<code>preset-env</code>中指明<code>ignoreBrowserslistConfig</code>，则忽略<code>.browserslistrc</code>的配置项。</p>
</blockquote>
<h3>useBuiltIns</h3>
<p>其次是用于指定<code>polyfill</code>方案的<code>useBuiltIns</code>，其默认值是<code>false</code>，在不主动<code>import</code>的情况下不使用<code>preset-env</code>来实现<code>polyfills</code>，只使用其默认的语法转换功能。</p>
<blockquote>
<p>如果使用默认值<code>false</code>，则应该避免在入口文件引入<code>polyfill</code>，使得打包体积过大。</p>
</blockquote>
<p>但是如果我们需要使用其<code>polyfill</code>功能，则可以选择两种方式：</p>
<ul>
<li>entry</li>
<li>usage</li>
</ul>
<p><code>entry</code>指的是将会根据浏览器目标环境(<code>targets</code>)的配置，引入全部浏览器暂未支持的<code>polyfill</code>模块，无论在项目中是否使用到。</p>
<p>先安装两个包：</p>
<pre><code class="language-bash">yarn add core-js@3 regenerator-runtime
</code></pre>
<p>我们需要做的就是在入口处引入<code>polyfill</code>（或者在 webpack 配置文件中新增这两个包作为额外的入口）:</p>
<pre><code class="language-js">import &quot;core-js/stable&quot;;
import &quot;regenerator-runtime/runtime&quot;;
</code></pre>
<p>对于项目开发来说，这种方案较为稳妥。尽管将浏览器暂不支持的模块全部引入可能会让某些项目中未使用的模块占据一定的额外体积，但是可以避免项目中引入的第三方库<code>polyfill</code>处理不当，导致引用异常。</p>
<p>其次，设置<code>useBuiltIns</code>的值为<code>usage</code>时，我们不需要手动在入口文件引入<code>polyfill</code>，<code>Babel</code>将会根据我们的代码使用情况自动注入<code>polyfill</code>，如此一来在打包的时候将会相对地减少打包体积。</p>
<p><code>唯一的问题</code>:当项目中引入的第三方库有<code>polyfill</code>处理不当的情况下，将会出现引用异常的问题，使用社区广泛使用的流行库能降低这个风险。</p>
<h3>corejs</h3>
<p>core-js 是完全模块化的 javascript 标准库。</p>
<p>推荐让浏览器的<code>polyfill</code>统一由<code>corejs</code>来管理。</p>
<blockquote>
<p>core-js v2 已经不再维护，推荐一致使用 v3 版本</p>
</blockquote>
<p>我们来看一个使用<code>entry</code>的整体<code>preset-env</code>配置示例：</p>
<pre><code class="language-js">// .babelrc
{
  &quot;presets&quot;: [
    [
      &quot;@babel/preset-env&quot;,
      {
        &quot;targets&quot;: {
          &quot;chrome&quot;: &quot;80&quot; // 推荐使用 .browserslistrc
        },
        &quot;useBuiltIns&quot;: &quot;entry&quot;,
        &quot;corejs&quot;: {
          &quot;version&quot;: 3, // 2 和 3 版本都需要手动安装库：yarn add core-js@3
          &quot;proposals&quot;: false
        }
      }
    ]
  ],
  &quot;plugins&quot;: []
}
</code></pre>
<blockquote>
<p>笔者个人而言，不推荐使用 proposals 功能，只使用最新规范中的特性。</p>
</blockquote>
<p>之后再在入口文件手动引入<code>polyfill</code>:</p>
<pre><code class="language-js">import &quot;core-js/stable&quot;;
import &quot;regenerator-runtime/runtime&quot;;
// other code
</code></pre>
<h2>Plugin-transform-runtime</h2>
<p>使用之前请安装库:</p>
<pre><code class="language-bash">yarn add @babel/runtime //默认 corejs为 false，如果使用 core-js v3 的 runtime，则需要安装 @babel/runtime-corejs3
yarn add -D babel-plugin-transform-runtime
</code></pre>
<p>之前提过<code>preset-env</code>的<code>polyfill</code>会污染全局，作为项目开发无可厚非，但是如果我们在开发提供给其他开发者使用的<code>library</code>，我想我们不应该污染全局，并且应该提供更好的打包体积和效率。</p>
<blockquote>
<p>A plugin that enables the re-use of Babel&#39;s injected helper code to save on codesize.</p>
</blockquote>
<p><code>plugin-transform-runtime</code>可以主要做了三件事：</p>
<ul>
<li>当开发者使用异步或生成器的时候，自动引入<code>@babel/runtime/regenerator</code>，开发者不必在入口文件做额外引入</li>
<li>提供沙盒环境，避免全局环境的污染</li>
<li>移除<code>babel</code>内联的<code>helpers</code>，统一使用<code>@babel/runtime/helpers</code>代替，减小打包体积</li>
</ul>
<p>当使用此方案时，不需要在入口文件处手动引入<code>core-js</code>和<code>regenerator-runtime</code>。详细的配置项建议在需要用的时候查看官方文档。</p>
<h2>总结</h2>
<p><code>@babel/preset-env</code>和<code>plugin-transform-runtime</code>二者都可以设置使用<code>corejs</code>来处理<code>polyfill</code>，二者各有使用场景，在项目开发和类库开发的时候可以使用不同的配置。</p>
<p><code>不要同时为二者配置core-js的功能</code>，以免产生复杂的不良后果。</p>
<p>最后，让我们来看看两个场景下的<code>babel</code>配置项：</p>
<h3>项目开发</h3>
<p><code>useBuiltIns</code>使用<code>usage</code>，尽量使用社区广泛使用的优质库以优化打包体积，不使用暂未进入规范的特性。<code>plugin-transform-runtime</code>只使用其移除内联复用的辅助函数的特性，减小打包体积。</p>
<pre><code class="language-js">{
  &quot;presets&quot;: [
    [
      &quot;@babel/preset-env&quot;,
      {
        // targets 官方推荐使用 .browserslistrc 配置
        &quot;useBuiltIns&quot;: &quot;usage&quot;,
        &quot;corejs&quot;: {
          &quot;version&quot;: 3,
          &quot;proposals&quot;: false
        }
      }
    ]
  ],
  &quot;plugins&quot;: [
    [
      &quot;@babel/plugin-transform-runtime&quot;,
      {
        &quot;corejs&quot;: false // 默认值，即使如此依然需要 yarn add @babel/runtime
      }
    ]
  ]
}
</code></pre>
<h3>类库开发</h3>
<p>类库开发尽量不使用污染全局环境的<code>polyfill</code>，因此<code>@babel/preset-env</code>只发挥语法转换的功能，<code>polyfill</code>由<code>plugin-transform-runtime</code>来处理，推荐使用<code>core-js@3</code>，并且不使用未进入规范的特性。</p>
<pre><code class="language-js">{
  &quot;presets&quot;: [
    [
      &quot;@babel/preset-env&quot;,
    ]
  ],
  &quot;plugins&quot;: [
    [
      &quot;@babel/plugin-transform-runtime&quot;,
      {
        &quot;corejs&quot;: {
          &quot;version&quot;: 3,
          &quot;proposals&quot;: true
        },
        &quot;useESModules&quot;: true
      }
    ]
  ]
}
</code></pre>
<p>总的来说，在配置开发环境的时候还是要参考官方文档的说明，这样才能减少出错的可能。</p>
<h2>参考</h2>
<ul>
<li><p><a href="https://babeljs.io/docs/en/babel-preset-env#usebuiltins">@babel/preset-env · Babel</a></p>
</li>
<li><p><a href="https://babeljs.io/docs/en/babel-plugin-transform-runtime#docsNav">@babel/plugin-transform-runtime · Babel</a></p>
</li>
<li><p><a href="https://zhuanlan.zhihu.com/p/361874935">吃一堑长一智系列: 99% 开发者没弄明白的 babel 知识</a></p>
</li>
</ul>
]]></content:encoded>
            <author>AaronConlon</author>
        </item>
    </channel>
</rss>