<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Booling✨</title>
        <link>https://blog.booling.cn/</link>
        <description>vitepress,blog,booling,bingling_sama</description>
        <lastBuildDate>Fri, 15 May 2026 06:28:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-CN</language>
        <copyright>Copyright (c) 2022-present Booling</copyright>
        <item>
            <title><![CDATA[是谁在我的 Edge 里吃了 4 个 G？]]></title>
            <link>https://blog.booling.cn/posts/development/edge-sw-storage-analysis</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/edge-sw-storage-analysis</guid>
            <pubDate>Thu, 07 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[一怒之下写了一个 Edge 网页缓存数据储存空间分析器]]></description>
            <content:encoded><![CDATA[<p>最近我 MacBook 的硬盘空间频频告急，作为一个每天都在跟代码打交道的开发者，我第一个想到的当然是 <code>node_modules</code> 和 Docker 镜像。然而清了一遍之后，空间依然捉襟见肘。</p>
<p>于是一个 <s>摸鱼</s> 的下午，我祭出了 <a href="https://github.com/ahgamut/mole" target="_blank" rel="noreferrer">mole</a> 这个用 Rust 写的终端磁盘分析工具，打算彻底搞清楚到底是谁在偷我的硬盘。结果一扫描，一个意想不到的选手浮出水面：</p>
<p><strong>Edge 浏览器，5 个 G。</strong></p>
<p>好家伙，一个浏览器而已，用得着囤这么多东西吗？你是打算在我硬盘上安家落户吗？😅</p>
<h2 id="真凶-service-worker-下的-cachestorage" tabindex="-1">真凶：Service Worker 下的 CacheStorage <a class="header-anchor" href="#真凶-service-worker-下的-cachestorage" aria-label="Permalink to &quot;真凶：Service Worker 下的 CacheStorage&quot;"></a></h2>
<p>顺着 <code>mole</code> 的结果往下 drill，我发现占用大头主要集中在下面这个目录：</p>
<div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>~/Library/Application Support/Microsoft Edge/Default/</span></span></code></pre>
</div><p>更具体的，是 <code>Default/CacheStorage/</code> 和 <code>Default/Service Worker/</code> 这两个目录。CacheStorage 里面有大量以 ID 命名的文件夹，每个里面都有 <code>.db</code> 和 <code>.blob</code> 文件，完全不知道对应的是哪个网站或扩展。</p>
<p>我平时 Edge 使用频率很高，标签页常年几十个开着。这种情况下，想肉眼看出哪个网站的 Service Worker 在疯狂缓存，简直是不可能的事情。</p>
<p>不过 Chromium 内核的浏览器有个好处 —— 它内置了几个&quot;上帝视角&quot;的调试页面：</p>
<table tabindex="0">
<thead>
<tr>
<th>内部页面</th>
<th>用途</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>edge://quota-internals</code></td>
<td>查看所有 Origin 的存储占用明细</td>
</tr>
<tr>
<td><code>edge://serviceworker-internals</code></td>
<td>查看所有注册的 Service Worker 及其状态</td>
</tr>
<tr>
<td><code>edge://extensions</code></td>
<td>扩展程序列表及其 ID</td>
</tr>
</tbody>
</table>
<p>通过这几个页面，理论上可以把每个存储块跟具体的网站或扩展对应起来。但问题是 —— 这些页面是给人看的，不是给机器读的。几百个 Origin 一个个手动对照，那不是要累死？🤦‍♂️</p>
<p>作为一个懒人程序员，我决定写个工具来自动化这个过程。</p>
<h2 id="技术方案设计" tabindex="-1">技术方案设计 <a class="header-anchor" href="#技术方案设计" aria-label="Permalink to &quot;技术方案设计&quot;"></a></h2>
<p>我和 Gemini 讨论了一下，梳理出了一个相对完整的方案。核心思路是 <strong>采集→解析→关联→报告</strong> 四层解耦：</p>
<div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>┌─────────────────────────────────┐</span></span>
<span class="line"><span>│          驱动层 (Bash)           │  ← 进程锁检查、环境清理</span></span>
<span class="line"><span>├─────────────────────────────────┤</span></span>
<span class="line"><span>│        采集层 (Node.js + Playwright) │  ← 模拟用户行为，绕过 edge:// 限制</span></span>
<span class="line"><span>├─────────────────────────────────┤</span></span>
<span class="line"><span>│          引擎层 (Rust)           │  ← 高性能解析、关联分析、异常识别</span></span>
<span class="line"><span>├─────────────────────────────────┤</span></span>
<span class="line"><span>│         表现层 (Rust/Bash)       │  ← 终端报表 &#x26; 清理建议</span></span>
<span class="line"><span>└─────────────────────────────────┘</span></span></code></pre>
</div><h3 id="采集层-绕过沙盒限制" tabindex="-1">采集层：绕过沙盒限制 <a class="header-anchor" href="#采集层-绕过沙盒限制" aria-label="Permalink to &quot;采集层：绕过沙盒限制&quot;"></a></h3>
<p>普通的网页 API 只能看到自己域下的数据，但 <code>edge://quota-internals</code> 是浏览器的&quot;特权页面&quot;，可以扫描所有域。关键在于：</p>
<ul>
<li><strong>必须使用非无头模式</strong>（<code>headless: false</code>），否则特权页面渲染不完整</li>
<li><strong>必须挂载真实 Profile 路径</strong>（<code>launchPersistentContext</code>），否则看到的是空数据</li>
<li>采集两个关键页面的 HTML 快照：
<ul>
<li><code>edge://quota-internals</code> → 存储占用数据</li>
<li><code>edge://serviceworker-internals</code> → SW 注册状态</li>
</ul>
</li>
<li>额外采集 <code>edge://extensions</code> → Extension ID ↔ 名称映射表</li>
</ul>
<h3 id="解析层-html-转结构化数据" tabindex="-1">解析层：HTML 转结构化数据 <a class="header-anchor" href="#解析层-html-转结构化数据" aria-label="Permalink to &quot;解析层：HTML 转结构化数据&quot;"></a></h3>
<p>采集到的 HTML 页面结构复杂，直接分析不现实。需要解析成结构化的 JSON 数据：</p>
<ul>
<li>统一单位（全部转为 Bytes，显示时动态换算 MiB/GiB）</li>
<li>URL 规范化（拆分协议、域名、端口）</li>
<li>以 Origin / Storage Key 为主键组织数据</li>
</ul>
<h3 id="关联层-交叉比对与异常检测" tabindex="-1">关联层：交叉比对与异常检测 <a class="header-anchor" href="#关联层-交叉比对与异常检测" aria-label="Permalink to &quot;关联层：交叉比对与异常检测&quot;"></a></h3>
<p>这是整个工具的核心。单纯的存储数据只能告诉你&quot;谁占了空间&quot;，但结合 Service Worker 的状态才能知道&quot;为什么占&quot;。我设计了几种检测规则：</p>
<table tabindex="0">
<thead>
<tr>
<th>检测类型</th>
<th>判断依据</th>
<th>风险等级</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>孤儿存储 (Orphaned)</strong></td>
<td><code>Usage &gt; 0</code> 但无活跃 SW 注册</td>
<td>⚠️ 中</td>
</tr>
<tr>
<td><strong>缓存膨胀 (Cache Bloat)</strong></td>
<td><code>CacheStorage / TotalUsage &gt; 90%</code> 且总占用超 500MB</td>
<td>🔴 高</td>
</tr>
<tr>
<td><strong>扩展残留 (Extension Residue)</strong></td>
<td><code>chrome-extension://</code> 协议但扩展已卸载</td>
<td>🗑️ 僵尸</td>
</tr>
</tbody>
</table>
<h3 id="报告层-清理指引" tabindex="-1">报告层：清理指引 <a class="header-anchor" href="#报告层-清理指引" aria-label="Permalink to &quot;报告层：清理指引&quot;"></a></h3>
<p>针对检测出来的问题，生成对应的清理建议。每条建议包含 <code>Storage.clearDataForOrigin()</code> 的 CDP 命令供参考（但不自动执行，确保安全）。</p>
<h2 id="两个小时出活" tabindex="-1">两个小时出活 <a class="header-anchor" href="#两个小时出活" aria-label="Permalink to &quot;两个小时出活&quot;"></a></h2>
<p>技术方案定下来后，我把整个方案丢给了 Codex。大概两个小时后，一个能跑的 MVP 就出来了。</p>
<p>这里得坦白一下技术选型上的一个小插曲。我当时在 Rust 和 Node.js 之间，倾向选了 Rust 做引擎层。老实说这个场景的数据量根本用不到 Rust 的高性能，几百 KB 的 HTML 用 cheerio 绰绰有余。选 Rust 纯粹是个人偏好，<s>外加想练练手</s>。不过 <code>scraper</code> crate 和 <code>comfy-table</code> crate 确实好用，后者画出来的终端表格比手撸 ASCII 美多了。</p>
<p>手动调了几轮之后，工具跑起来是这样的效果：</p>
<div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>Parsed: 4 quota records, 2 service workers, 1 extensions</span></span>
<span class="line"><span>Total usage: 712.00 MiB</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Top Storage Consumers</span></span>
<span class="line"><span>┌─────────────────────────────────┬────────────┬──────────────┬───────────┐</span></span>
<span class="line"><span>│ Origin                          ┆ Usage      ┆ CacheStorage ┆ Kind      │</span></span>
<span class="line"><span>╞═════════════════════════════════╪════════════╪══════════════╪═══════════╡</span></span>
<span class="line"><span>│ https://example.com             ┆ 640.00 MiB ┆ 600.00 MiB   ┆ Website   │</span></span>
<span class="line"><span>├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤</span></span>
<span class="line"><span>│ chrome-extension://missing999   ┆ 18.00 MiB  ┆ 18.00 MiB    ┆ Extension │</span></span>
<span class="line"><span>└─────────────────────────────────┴────────────┴──────────────┴───────────┘</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Orphaned Storage Candidates</span></span>
<span class="line"><span>┌───────────────────────────────┬───────────┬──────────────┬───────────┐</span></span>
<span class="line"><span>│ Origin                        ┆ Usage     ┆ CacheStorage ┆ Kind      │</span></span>
<span class="line"><span>╞═══════════════════════════════╪═══════════╪══════════════╪═══════════╡</span></span>
<span class="line"><span>│ https://orphan.test           ┆ 12.00 MiB ┆ 1.00 MiB     ┆ Website   │</span></span>
<span class="line"><span>├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤</span></span>
<span class="line"><span>│ chrome-extension://missing999 ┆ 18.00 MiB ┆ 18.00 MiB    ┆ Extension │</span></span>
<span class="line"><span>└───────────────────────────────┴───────────┴──────────────┴───────────┘</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Cache Bloat Risk Candidates</span></span>
<span class="line"><span>┌─────────────────────┬────────────┬──────────────┬─────────┐</span></span>
<span class="line"><span>│ Origin              ┆ Usage      ┆ CacheStorage ┆ Kind    │</span></span>
<span class="line"><span>╞═════════════════════╪════════════╪══════════════╪═════════╡</span></span>
<span class="line"><span>│ https://example.com ┆ 640.00 MiB ┆ 600.00 MiB   ┆ Website │</span></span>
<span class="line"><span>└─────────────────────┴────────────┴──────────────┴─────────┘</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Cleanup Guidance</span></span>
<span class="line"><span>- Inspect and clear site data for https://orphan.test from Edge settings.</span></span>
<span class="line"><span>- Inspect and clear site data for chrome-extension://missing999 from Edge settings.</span></span>
<span class="line"><span>- Inspect and clear site data for https://example.com from Edge settings.</span></span></code></pre>
</div><p>MVP 验证通过，测试数据能正确检测出孤儿存储、扩展残留和缓存膨胀。接下来就是真刀真枪上场了。</p>
<h2 id="飞书你干得好啊" tabindex="-1">飞书你干得好啊 <a class="header-anchor" href="#飞书你干得好啊" aria-label="Permalink to &quot;飞书你干得好啊&quot;"></a></h2>
<p>我把脚本指向了我的真实 Edge Profile 目录，按下回车，几秒后结果就出来了。</p>
<p>Edge 一共吃掉了 <strong>5 个多 G</strong>。</p>
<p>其中，<strong>3.9G 来自飞书网页版</strong>。</p>
<p>我本来还怀疑是哪个浏览器扩展出了 bug，有内存泄漏问题或者保存了过多的日志，结果没想到是飞书干的好事。</p>
<p>其实我一开始使用的是飞书桌面客户端，但是也经常发现它会囤积更新数据，占用的储存有时能达到 <strong>10个G</strong> 以上，所以我切换到了网页版，没想到它一个网页端竟然也能给我存近 4G 的数据……</p>
<p>我想飞书的 Service Worker 大概是这种逻辑：</p>
<div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 纯属脑补，不代表真实代码</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">self.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'fetch'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">event</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 管他三七二十一，先缓存了再说</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  event.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">respondWith</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cacheThenNetwork</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(event.request))</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 清理策略？那是什么，能吃吗？</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
</div><p>也不是不能理解。飞书作为协作办公平台，文件、图片、聊天记录、文档协作数据量本来就大。用 CacheStorage API 做离线缓存确实能提升体验 —— 但这个膨胀幅度属实有点离谱了。三个月前的对话附件和头像，真的有必要一直留在本地吗？😅</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;"></a></h2>
<p>这次折腾，从发现问题到最后定位真凶，大概花了一个下午 + 一个晚上。回头看看，整个过程挺有意思的：</p>
<ol>
<li><strong>问题发现</strong>：<code>mole</code> 扫盘 → Edge 5GB 异常占用</li>
<li><strong>线索追踪</strong>：手动查看 <code>edge://</code> 内部页面 → 锁定 CacheStorage 和 Service Worker</li>
<li><strong>方案设计</strong>：四层解耦架构，数据采集 + 交叉比对 + 异常检测</li>
<li><strong>极速实现</strong>：Codex 生成 MVP，手动调优</li>
<li><strong>终极结果</strong>：抓到真凶 —— 飞书网页版 3.9G</li>
</ol>
<p>写工具的过程本身也值回票价了。说实话，如果不写这个工具，我可能永远想不到要去 <code>edge://quota-internals</code> 看一眼，大概只会粗暴地清理全站数据完事。而有了工具之后，不仅精准定位到冤大头，还顺带发现了一些已卸载扩展的残留文件。</p>
<p>后续如果有时间，我打算把这个工具再打磨一下 —— 加个跨浏览器支持（Chrome/Brave 也就改几个路径和 URL），再搞个 HTML 可视化报告。不过现在嘛，先把飞书的缓存清了再说 🏃‍♂️💨</p>
<p>工具我也已经开源到 <a href="https://github.com/bingling-sama/swsx" target="_blank" rel="noreferrer">GitHub</a> 了，感兴趣的可以去玩一下，不过目前还非常毛坯，需要再打磨一段时间，欢迎大家提出 issue，我 <s>应该</s> 会及时响应。</p>
<hr>
<p><em>如果你也好奇自己的 Edge 到底被什么网站悄悄吃掉了硬盘，不妨打开 <code>edge://quota-internals</code> 看一眼。说不定也会有&quot;惊喜&quot; 🙃</em></p>
]]></content:encoded>
            <author>Booling</author>
            <category>FrontEnd</category>
            <category>System</category>
            <category>Browser</category>
            <category>Rust</category>
            <category>Node.js</category>
            <category>Edge</category>
        </item>
        <item>
            <title><![CDATA[ import.meta.url 是啥子？]]></title>
            <link>https://blog.booling.cn/posts/development/import-meta-url</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/import-meta-url</guid>
            <pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从 ECMAScript 规范角度全面理解 import.meta 与 import.meta.url，涵盖浏览器、Node.js、Deno、Bun 各宿主环境的差异与约定，以及在 Vite 等构建工具中的实际应用。]]></description>
            <content:encoded><![CDATA[<h1 id="import-meta-url-是啥子" tabindex="-1"><code>import.meta.url</code> 是啥子？ <a class="header-anchor" href="#import-meta-url-是啥子" aria-label="Permalink to &quot;`import.meta.url` 是啥子？&quot;"></a></h1>
<p>我一直不太清楚 <code>import.meta.url</code> 到底是怎么来的，总觉得它是个黑魔法。直到某天我翻开了 ECMAScript 规范，才发现 —— 原来人家是 ESM 的正规军，正儿八经的语言特性 👮。</p>
<p>今天就从头捋一捋：<code>import.meta</code> 从哪来、各环境有啥区别、实战中怎么用。</p>
<h2 id="import-meta-规范只给了一个空壳子-🐚" tabindex="-1"><code>import.meta</code>：规范只给了一个空壳子 🐚 <a class="header-anchor" href="#import-meta-规范只给了一个空壳子-🐚" aria-label="Permalink to &quot;`import.meta`：规范只给了一个空壳子 🐚&quot;"></a></h2>
<p>翻开 <a href="https://tc39.es/ecma262/#sec-meta-properties" target="_blank" rel="noreferrer">ECMAScript 语言规范</a>，你会发现整个语言里只有两个<strong>元属性（Meta Property）</strong>：</p>
<ul>
<li><code>new.target</code> — 判断构造函数有没有被 <code>new</code> 调用</li>
<li><code>import.meta</code> — 我们的主角</li>
</ul>
<p>语法上长这样：</p>
<div class="language-bnf vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bnf</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>MetaProperty ::=  import . meta</span></span></code></pre>
</div><p>几个硬性规定，得背下来：</p>
<ul>
<li><code>import</code> 在这里<strong>不是关键字</strong>，而是 <code>MetaProperty</code> 的起始标记（<s>语法糖呗</s>）</li>
<li><strong>只能在模块代码中使用</strong>，在传统 Script 里用？直接给你 <code>SyntaxError</code> 💥</li>
<li>关键是：<strong>规范本身不定义它有哪些属性</strong>，这事儿全交给宿主环境决定</li>
</ul>
<p>简单说：语言标准就搭了个骨架，肉长什么样，由运行时说了算。</p>
<h2 id="设计哲学-host-hooks-各家装各家的修-🏠" tabindex="-1">设计哲学：Host Hooks，各家装各家的修 🏠 <a class="header-anchor" href="#设计哲学-host-hooks-各家装各家的修-🏠" aria-label="Permalink to &quot;设计哲学：Host Hooks，各家装各家的修 🏠&quot;"></a></h2>
<p>ECMA-262 定义了一个抽象操作 <code>HostGetImportMetaProperties</code>：</p>
<blockquote>
<p><strong>HostGetImportMetaProperties(importMeta, moduleRecord)</strong> 是宿主环境实现的抽象操作。宿主通过这个 Hook 往 <code>import.meta</code> 对象上注入属性。</p>
</blockquote>
<p>这就好比房东给了你一间毛坯房，装修成啥样全看你自己：</p>
<div class="language-txt vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>ECMAScript 规范:</span></span>
<span class="line"><span>  └── import.meta 是一个空对象</span></span>
<span class="line"><span></span></span>
<span class="line"><span>浏览器 (HTML Spec):</span></span>
<span class="line"><span>  └── 注入 url, resolve</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Node.js:</span></span>
<span class="line"><span>  └── 注入 url, resolve, dirname, filename</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Deno:</span></span>
<span class="line"><span>  └── 注入 url, main, resolve</span></span>
<span class="line"><span></span></span>
<span class="line"><span>Bun:</span></span>
<span class="line"><span>  └── 注入 url, resolve, dir, file, path, env</span></span></code></pre>
</div><p>所以下次有人再说&quot;<code>import.meta</code> 是哪个框架的私有财产&quot;，你可以微笑着告诉他：<strong>这是 ECMAScript 标准的 API，只不过各家往里塞的东西不一样</strong> 😎</p>
<h2 id="各家塞了些啥-我们来逐个看看" tabindex="-1">各家塞了些啥？我们来逐个看看 <a class="header-anchor" href="#各家塞了些啥-我们来逐个看看" aria-label="Permalink to &quot;各家塞了些啥？我们来逐个看看&quot;"></a></h2>
<h3 id="浏览器" tabindex="-1">浏览器 <a class="header-anchor" href="#浏览器" aria-label="Permalink to &quot;浏览器&quot;"></a></h3>
<p>根据 <a href="https://html.spec.whatwg.org/multipage/webappapis.html#hostgetimportmetaproperties" target="_blank" rel="noreferrer">WHATWG HTML Spec</a>，浏览器只给了两个属性，非常克制：</p>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 当前模块的绝对 URL</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.url);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → 'https://example.com/js/app.js'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 基于当前模块解析相对路径</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> resolved</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./helper.js'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → 'https://example.com/js/helper.js'</span></span></code></pre>
</div><ul>
<li><strong><code>url</code></strong>：当前模块的完整绝对 URL（类型 <code>string</code>）</li>
<li><strong><code>resolve</code></strong>：一个函数，把相对路径解析成基于当前模块的绝对 URL。Chrome/Edge 105+、Firefox 106+、Safari 16.4+ 开始支持</li>
</ul>
<h3 id="node-js" tabindex="-1">Node.js <a class="header-anchor" href="#node-js" aria-label="Permalink to &quot;Node.js&quot;"></a></h3>
<p>Node.js 在浏览器基础上，加了一堆 CJS 时代的老朋友：</p>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Node.js ESM 模式</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.url);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → 'file:///Users/alice/project/src/index.mjs'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.dirname);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → '/Users/alice/project/src'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.filename);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → '/Users/alice/project/src/index.mjs'</span></span></code></pre>
</div><ul>
<li><strong><code>dirname</code></strong>：等价于 CJS 的 <code>__dirname</code>（Node.js 21.2+）</li>
<li><strong><code>filename</code></strong>：等价于 CJS 的 <code>__filename</code>（Node.js 21.2+）</li>
<li><strong><code>resolve</code></strong>：行为类似浏览器版，但走的是 Node.js 的模块解析算法（会在 <code>node_modules</code> 里翻）</li>
</ul>
<blockquote>
<p>如果还在用 21.2 以下的 Node.js，也别慌，自己手搓一手：</p>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { fileURLToPath } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'url'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { dirname } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'path'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> __filename</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> fileURLToPath</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.url);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> __dirname</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> dirname</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(__filename);</span></span></code></pre>
</div><p><s>好家伙，又回到了手写 CJS 兼容层的年代</s></p>
</blockquote>
<h3 id="deno" tabindex="-1">Deno <a class="header-anchor" href="#deno" aria-label="Permalink to &quot;Deno&quot;"></a></h3>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.url;    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → 'file:///...' | 'https://...'</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.main;   </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → true 如果当前模块是入口模块</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">resolve</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./x.ts'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span></code></pre>
</div><p><code>main</code> 是 Deno 的特色属性，类似 Python 的 <code>if __name__ == '__main__'</code>，用来判断当前模块是不是入口文件，挺实用的。</p>
<h3 id="bun" tabindex="-1">Bun <a class="header-anchor" href="#bun" aria-label="Permalink to &quot;Bun&quot;"></a></h3>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.url;     </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → 'file:///...'</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.dir;     </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → 目录路径</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.file;    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → 当前文件名</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.path;    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → 完整文件路径</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.env;     </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// → Bun.env 的访问器</span></span></code></pre>
</div><p>Bun 最&quot;霸道&quot;，直接塞了四个路径相关属性再加一个 <code>env</code>，<s>不愧是&quot;我全都要&quot;的运行时</s>。</p>
<h2 id="为什么-esm-不直接使用-dirname" tabindex="-1">为什么 ESM 不直接使用 <code>__dirname</code> <a class="header-anchor" href="#为什么-esm-不直接使用-dirname" aria-label="Permalink to &quot;为什么 ESM 不直接使用 `__dirname`&quot;"></a></h2>
<p>这个问题我当年也困惑过，CJS 时代多爽啊，直接 <code>__dirname</code> 就拿当前目录了。到了 ESM，还得绕个弯：</p>
<table tabindex="0">
<thead>
<tr>
<th>需求</th>
<th>CJS 方案</th>
<th>ESM 方案</th>
</tr>
</thead>
<tbody>
<tr>
<td>获取当前文件路径</td>
<td><code>__filename</code>, <code>__dirname</code></td>
<td><code>import.meta.url</code> + <code>fileURLToPath</code></td>
</tr>
<tr>
<td>基于当前模块解析路径</td>
<td><code>require.resolve()</code></td>
<td><code>import.meta.resolve()</code></td>
</tr>
<tr>
<td>获取模块元信息</td>
<td>无标准方式</td>
<td><code>import.meta.url</code> 统一入口</td>
</tr>
<tr>
<td>判断是否为入口模块</td>
<td><code>require.main === module</code></td>
<td><code>import.meta.main</code>（Deno）</td>
</tr>
</tbody>
</table>
<p>原因其实挺简单：</p>
<ol>
<li><strong>全局变量污染</strong>：<code>__dirname</code> 在 CJS 里是通过函数包装注入的，本质上不算真正的全局变量，但这套在浏览器里就行不通了</li>
<li><strong>浏览器兼容性</strong>：<code>__dirname</code> 在浏览器中没有意义，而 <code>import.meta.url</code> 用统一的 URL 格式覆盖所有环境，从 <code>file://</code> 到 <code>https://</code> 全通吃</li>
<li><strong>可扩展性</strong>：<code>import.meta</code> 作为可扩展对象，宿主随便往里加东西，比不断新增全局变量优雅得多</li>
</ol>
<h2 id="跨环境通用约定" tabindex="-1">跨环境通用约定 <a class="header-anchor" href="#跨环境通用约定" aria-label="Permalink to &quot;跨环境通用约定&quot;"></a></h2>
<h3 id="import-meta-url-必须是绝对-url" tabindex="-1"><code>import.meta.url</code> 必须是绝对 URL <a class="header-anchor" href="#import-meta-url-必须是绝对-url" aria-label="Permalink to &quot;`import.meta.url` 必须是绝对 URL&quot;"></a></h3>
<p>不管模块是从本地文件系统加载、HTTP 远程加载、还是 data URI，<code>import.meta.url</code> 始终是绝对形式。这就保证了基于它的路径解析永远不会迷路 🗺️。</p>
<h3 id="new-url-relative-import-meta-url-是最稳的模式" tabindex="-1"><code>new URL(relative, import.meta.url)</code> 是最稳的模式 <a class="header-anchor" href="#new-url-relative-import-meta-url-是最稳的模式" aria-label="Permalink to &quot;`new URL(relative, import.meta.url)` 是最稳的模式&quot;"></a></h3>
<p>由于 <code>import.meta.url</code> 是绝对 URL，而 <code>URL</code> 构造函数在所有现代运行时上都有，这个模式就成了获取同目录资源的<strong>通用、可靠、标准</strong>方式：</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 引用同目录下的配置文件——跨环境通用，稳如老狗 🐶</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> configPath</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> URL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./config.json'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.url);</span></span></code></pre>
</div><p>浏览器里得到 <code>http://...</code>，Node.js 里得到 <code>file:///...</code>，都能直接传给对应的 I/O API。</p>
<h3 id="import-meta-resolve-的标准化趋势" tabindex="-1"><code>import.meta.resolve()</code> 的标准化趋势 <a class="header-anchor" href="#import-meta-resolve-的标准化趋势" aria-label="Permalink to &quot;`import.meta.resolve()` 的标准化趋势&quot;"></a></h3>
<p><code>import.meta.resolve()</code> 最初是浏览器专用，现在已经成了所有主流 ESM 宿主环境的标准配置。语义是<strong>相对于当前模块解析模块说明符</strong>，返回绝对 URL。</p>
<p>不过各家的返回类型有差异：</p>
<ul>
<li>浏览器：返回字符串</li>
<li>Node.js：返回 URL 对象（可用 <code>.href</code> 拿字符串）</li>
<li>规范允许返回任何对象，只要 <code>toString()</code> 能返回有效 URL</li>
</ul>
<h3 id="自定义属性的命名约定" tabindex="-1">自定义属性的命名约定 <a class="header-anchor" href="#自定义属性的命名约定" aria-label="Permalink to &quot;自定义属性的命名约定&quot;"></a></h3>
<p>由于 <code>import.meta</code> 是可变对象，宿主可以动态加属性。社区有个默契：自定义属性最好用大写或特殊前缀，避免以后冲突。比如 Vite 的 <code>import.meta.env</code>、<code>import.meta.hot</code> 就是典型。</p>
<h2 id="构建工具中的实际应用" tabindex="-1">构建工具中的实际应用 <a class="header-anchor" href="#构建工具中的实际应用" aria-label="Permalink to &quot;构建工具中的实际应用&quot;"></a></h2>
<p>前端构建工具（Vite、Webpack、Rollup）在开发阶段保留 <code>import.meta</code> 的语义，但在生产构建时会把相关模式<strong>静态替换</strong>成最终路径。</p>
<h3 id="vite-中的常见用法" tabindex="-1">Vite 中的常见用法 <a class="header-anchor" href="#vite-中的常见用法" aria-label="Permalink to &quot;Vite 中的常见用法&quot;"></a></h3>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 1. 引用同目录静态资源</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> imgUrl</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> URL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./logo.png'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.url).href;</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 开发: http://localhost:5173/src/logo.png</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 构建: /assets/logo.a1b2c3d4.png</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 2. 创建 Web Worker</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> worker</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Worker</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> URL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./worker.ts'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.url), {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  type: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'module'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">});</span></span></code></pre>
</div><p>Vite 构建时会<strong>静态分析</strong> <code>new URL(..., import.meta.url)</code> 这种模式，替换成生产环境的最终资源路径并自动加哈希。但这里有个坑：路径参数必须是<strong>字面量字符串</strong>，不能是变量，否则 Vite 就没法静态解析了 —— 我第一次踩到这坑的时候，控制台直接报了警告 🤦。</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// ❌ 这样不行</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> name</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> './logo.png'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> URL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(name, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.url);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// ✅ 必须这样</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> URL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'./logo.png'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">meta</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.url);</span></span></code></pre>
</div><h3 id="构建工具的通用约束" tabindex="-1">构建工具的通用约束 <a class="header-anchor" href="#构建工具的通用约束" aria-label="Permalink to &quot;构建工具的通用约束&quot;"></a></h3>
<ul>
<li>把 <code>import.meta.url</code> 替换成绝对资源路径字符串</li>
<li>把 <code>new URL(relative, import.meta.url)</code> 整体替换成构建后的资源 URL</li>
<li>无法静态分析的动态用法会直接警告</li>
</ul>
<p>这套约定保证了代码在不同部署环境（ESM、CJS、IIFE）下都能正常跑。<s>果然，各家框架偷偷约好了一起玩这套</s></p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;"></a></h2>
<p>一条主线贯穿全文：</p>
<blockquote>
<p><strong><code>import.meta</code> 是 ECMAScript 规范定义的元属性，由宿主环境决定具体属性内容；<code>import.meta.url</code> 是所有宿主环境都支持的、表示当前模块绝对 URL 的共同约定。</strong></p>
</blockquote>
<p>理解这一点后，不管是写浏览器端代码、Node.js 工具链，还是配置 Vite 构建，都能对 <code>import.meta.url</code> 的来龙去脉心中有数。</p>
<p>下次面试官问起来，你可以淡定地甩一句：&quot;这是 ECMAScript 的元属性，由 <code>HostGetImportMetaProperties</code> Hook 实现，各家注入的属性不同……&quot; 然后看他目瞪口呆的样子 😄</p>
<hr>
<h3 id="参考" tabindex="-1">参考 <a class="header-anchor" href="#参考" aria-label="Permalink to &quot;参考&quot;"></a></h3>
<ul>
<li><a href="https://tc39.es/ecma262/#sec-meta-properties" target="_blank" rel="noreferrer">ECMAScript Language Specification — Meta Properties</a></li>
<li><a href="https://html.spec.whatwg.org/multipage/webappapis.html#hostgetimportmetaproperties" target="_blank" rel="noreferrer">WHATWG HTML Spec — HostGetImportMetaProperties</a></li>
<li><a href="https://nodejs.org/api/esm.html#importmeta" target="_blank" rel="noreferrer">Node.js — import.meta</a></li>
<li><a href="https://vitejs.dev/guide/assets.html#new-url-url-import-meta-url" target="_blank" rel="noreferrer">Vite — Static Asset Handling</a></li>
</ul>
]]></content:encoded>
            <author>Booling</author>
            <category>Development</category>
            <category>FrontEnd</category>
            <category>Study</category>
            <category>JavaScript</category>
        </item>
        <item>
            <title><![CDATA[React Native 和 Expo 的渲染原理]]></title>
            <link>https://blog.booling.cn/posts/development/react-native-expo-rendering</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/react-native-expo-rendering</guid>
            <pubDate>Sun, 26 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[记录一下 React Native 和 Expo 在移动端到底是怎么把前端代码跑到手机上的]]></description>
            <content:encoded><![CDATA[<h1 id="react-native-和-expo-的渲染原理" tabindex="-1">React Native 和 Expo 的渲染原理 <a class="header-anchor" href="#react-native-和-expo-的渲染原理" aria-label="Permalink to &quot;React Native 和 Expo 的渲染原理&quot;"></a></h1>
<p>答应大家下次要分享前端技术在移动端、跨端领域的应用，所以这次给大家带来 RN 和 Expo 的技术分享。</p>
<p>相信大家刚刚开始看 React Native 和 Expo 的时候，和我一样一直有一个问题：</p>
<blockquote>
<p>为什么我写的是 React 组件，最后却能直接在手机上显示出来？</p>
</blockquote>
<p>这个问题看起来不复杂，但真要往下追，就会牵扯到很多东西。React、React Native、Expo、Metro、iOS、Android，这些概念如果放在一起看，很容易让人混掉。</p>
<p>所以今天我主要想讲清楚的是：React Native 到底是怎么把前端代码渲染到手机上的，Expo 又在这个过程中负责什么。</p>
<h2 id="react-native-的是干啥滴" tabindex="-1">React Native 的是干啥滴 <a class="header-anchor" href="#react-native-的是干啥滴" aria-label="Permalink to &quot;React Native 的是干啥滴&quot;"></a></h2>
<p>首先，什么是 React Native 的渲染？</p>
<p>React Native 的本职工作，是把 React 代码变成移动端的原生视图。它不是在手机上跑一个网页，也不是把 HTML 页面包起来再显示出来，而是把 JSX 描述的结构，转成 iOS 和 Android 能直接处理的原生组件。</p>
<p>也就是说，开发者写出来的并不是最终画面本身，而是一份 UI 描述。React Native 负责把这份描述翻译成平台原生控件，最后再由 iOS 和 Android 把界面画到屏幕上。</p>
<p>这一点和 Web 里的 React 很不一样。Web React 最终面对的是 DOM，而 React Native 面对的是原生视图。两者都使用 React 的写法，但落到的目标完全不同。</p>
<h2 id="react-native-里的-ui-不是-dom" tabindex="-1">React Native 里的 UI 不是 DOM <a class="header-anchor" href="#react-native-里的-ui-不是-dom" aria-label="Permalink to &quot;React Native 里的 UI 不是 DOM&quot;"></a></h2>
<p>那么其次，React Native 和 Web React 的区别到底在哪里？</p>
<p>最直接的区别，就是它们最终渲染的目标不同。Web 上的 React 需要把组件渲染到 DOM 里，React Native 需要把组件渲染到原生控件里。</p>
<p>例如一个输入框，在 iOS 上会对应到 <code>UITextField</code> 一类的原生控件，在 Android 上会对应到对应平台的输入组件。开发者写的是同一套 React 组件，但真正执行渲染的是不同平台自己的 UI 系统。</p>
<p>这也是 React Native 和 Web 最大的差异之一。React Native 并没有把移动端做成一个网页，它只是让开发者用 React 的方式去描述原生界面。</p>
<h2 id="渲染流程" tabindex="-1">渲染流程 <a class="header-anchor" href="#渲染流程" aria-label="Permalink to &quot;渲染流程&quot;"></a></h2>
<p>那么接下来就是 React Native 的渲染流程。</p>
<p>React Native 现在把这个过程拆成三个阶段：<code>Render</code>、<code>Commit</code>、<code>Mount</code>。</p>
<p>如果只看这三个词，会觉得它们都很抽象。但如果把它放到一次真实的状态更新里看，事情就清楚很多了。</p>
<p>比如页面上有一个按钮，点击以后计数加一。这个动作发生之后，React 先会知道“状态变了”，然后开始重新计算这个组件树应该长成什么样。这个时候还没有真正去改屏幕上的内容，先改的是内部结构。</p>
<p>React Native 做的，就是把这次变化从 JavaScript 侧一路传到原生侧。它不是一下子把整块界面重画一遍，而是先把新的结构算出来，再把需要更新的部分提交出去，最后挂到原生视图上。</p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Counter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">count</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">setCount</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useState</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>{count}&#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> title</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"add"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onPress</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setCount</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(count </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)} /></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  )</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><p>当 <code>count</code> 变化以后，React 会重新计算这段 JSX 对应的 React Element Tree。<br>
React Native 再把这个新树和上一次的树比较，找出需要更新的地方。最后它把这些变化翻译成原生层能执行的最小变更。</p>
<p>这个过程里，真正重要的不是“重新画一遍”，而是“只更新发生变化的那部分”。</p>
<h3 id="render" tabindex="-1">Render <a class="header-anchor" href="#render" aria-label="Permalink to &quot;Render&quot;"></a></h3>
<p>首先是 Render。</p>
<p>这一步发生在 JavaScript 侧。React 先根据组件和状态变化，生成一棵 React Element Tree。在新架构里，这棵树还会对应到 C++ 层的 Shadow Tree。</p>
<p>这个阶段做的是结构计算。页面里有哪些组件，它们之间怎么嵌套，属性是什么，状态更新以后应该生成什么样的新树，这些事情都在这个阶段处理。</p>
<p>如果页面里有多个组件，React 并不会因为某一个地方更新了，就傻乎乎地把整个页面重新拼一遍。它会先根据新的状态，算出新的结构，然后和上一次的结构做比较。这样一来，哪些地方变了，哪些地方没变，就都能被识别出来。</p>
<p>所以 Render 阶段更像是在整理一份新的 UI 草稿。它还没真正碰到屏幕，但已经把接下来要显示什么内容算好了。</p>
<p>这里还可以顺手看一下树的样子。比如下面这个结构：</p>
<div class="language-jsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">jsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Hello&#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
</div><p>React 侧会先得到对应的元素树，C++ 侧再生成 Shadow Tree。Shadow Tree 里保存的是组件层级、样式信息和后续布局所需的数据。它不是最终的屏幕树，但它是后面布局和挂载的基础。</p>
<h3 id="commit" tabindex="-1">Commit <a class="header-anchor" href="#commit" aria-label="Permalink to &quot;Commit&quot;"></a></h3>
<p>然后是 Commit。</p>
<p>当新的树准备好之后，React Native 会把这棵树提交给后续阶段处理，并触发布局计算。这个阶段还没有真正把内容画到屏幕上，但已经把下一次要挂载的内容准备好了。</p>
<p>这里的重点不是“有没有画出来”，而是“这份更新是不是已经准备好交给原生层了”。Commit 发生以后，React Native 会知道这次更新可以进入下一步了，接下来就是把布局结果和视图变化落到原生对象上。</p>
<p>如果把 Render 当成“写草稿”，那 Commit 就是“把草稿交给排版和印刷部门处理”。这一步不负责最后展示，但它决定了接下来的内容能不能顺利落到屏幕上。</p>
<p>Commit 阶段主要做两件事：布局计算和 tree promotion。</p>
<p>布局计算会把每个 Shadow Node 的尺寸和位置算出来。React Native 这里使用的是 Yoga。Yoga 处理的是一棵 layout tree，开发者在样式里写的 <code>flex</code>、<code>width</code>、<code>height</code>、<code>padding</code>、<code>margin</code> 等信息，都会被送进这套布局系统里。</p>
<p>例如：</p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> style</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{{ flex: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }}></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> style</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{{ height: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">100</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }} /></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> style</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{{ flex: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }} /></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
</div><p>这段代码里，外层容器先拿到整块可用空间，内部两个子节点再按照各自的约束分配位置和大小。这个过程不是浏览器的 CSS 布局引擎在做，而是 Yoga 在做。</p>
<p>布局结果算出来以后，新的 Shadow Tree 会被提升为 next tree，等待 Mount 阶段被真正挂载。</p>
<h3 id="mount" tabindex="-1">Mount <a class="header-anchor" href="#mount" aria-label="Permalink to &quot;Mount&quot;"></a></h3>
<p>最后是 Mount。</p>
<p>这一步会把已经计算好的结果真正挂到原生视图上。到了这里，iOS 或 Android 才会把界面显示到屏幕上。</p>
<p>Mount 阶段做的事情，已经很接近“把页面真正摆出来”了。原生视图被创建、更新、插入、删除，最后形成用户在手机上看到的那一帧界面。前面所有的计算，到了这里才真正变成了肉眼可见的内容。</p>
<p>也就是说，从开发者写代码，到用户看到界面，中间并不是直接“渲染一下”就结束了，而是经历了结构生成、提交、布局、挂载这一整套过程。</p>
<p>这套过程看起来繁琐，但它做的事情其实很明确：先把 UI 的结构算清楚，再把结构交给布局系统，最后把结果落到平台自己的原生视图上。</p>
<p>Mount 阶段还会做 tree diffing。React Native 会把“上一次已经挂载的树”和“这一次准备挂载的新树”做比较，然后计算出一组原子级别的 mutation，例如 <code>createView</code>、<code>updateView</code>、<code>removeView</code> 之类的操作。</p>
<p>这个 diff 过程是在 C++ 里完成的。它的目标很直接：不要把整棵树推倒重来，只把真正变化的部分转成原生操作。</p>
<div class="language-txt vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>previous tree  ->  diff  ->  mutation list  ->  host views</span></span></code></pre>
</div><p>如果一个节点只是改了背景色，React Native 不会把整页重新创建一遍，它只会生成对应的更新操作。<br>
如果一个节点根本没有变化，diff 之后就不会产生多余的原生指令。</p>
<p>View Flattening 也是 Mount 阶段里很重要的优化。React 的组件树经常会比最终需要显示的原生视图树更深，其中有不少节点只是为了组织布局，并不真正负责显示内容。View Flattening 会把这类节点折叠掉，减少不必要的原生视图创建。</p>
<p>这也是为什么 React Native 的渲染链路里，结构树、布局树和最终的 host view tree 并不是一回事。它们的层级不同，职责也不同。</p>
<p>到这里，React Native 这一侧的流程已经比较完整了，但这些结果还只是“准备好”，真正落到屏幕上，还要看原生平台怎么接住这些更新。</p>
<h2 id="这些更新在原生侧是怎么生效的" tabindex="-1">这些更新在原生侧是怎么生效的？ <a class="header-anchor" href="#这些更新在原生侧是怎么生效的" aria-label="Permalink to &quot;这些更新在原生侧是怎么生效的？&quot;"></a></h2>
<p>如果只停留在 React 树这一侧，很多细节还是不够完整。真正把东西画到屏幕上的，还是原生平台自己的视图系统。</p>
<p>在 React Native 里，最基础的几个组件都对应着平台自己的原生控件。</p>
<ul>
<li><code>View</code> 会对应到 iOS 的 <code>UIView</code>，在 Android 上通常对应 <code>View</code> 或 <code>ViewGroup</code></li>
<li><code>Text</code> 由平台的原生文本渲染系统处理</li>
<li><code>TextInput</code> 会接到平台原生输入控件</li>
<li><code>Image</code> 会走平台自己的图片解码、缓存和绘制链路</li>
</ul>
<p>所以当你写下面这种代码的时候：</p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> style</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{{ padding: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">12</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, backgroundColor: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'#fff'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }}></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Hello&#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">TextInput</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> placeholder</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"type here"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
</div><p>React Native 并不是把这段 JSX 变成某个中间页面对象，而是把它拆成一组原生视图操作：</p>
<ul>
<li>创建一个容器视图</li>
<li>给容器设置 padding 和背景色</li>
<li>创建文本节点并交给原生文本渲染</li>
<li>创建输入框并连接键盘、焦点和输入事件</li>
</ul>
<p>这就是 React Native 最核心的一点：JS 层写的是声明式 UI，真正执行的是原生视图系统。</p>
<h3 id="原生视图管理" tabindex="-1">原生视图管理 <a class="header-anchor" href="#原生视图管理" aria-label="Permalink to &quot;原生视图管理&quot;"></a></h3>
<p>在原生侧，每个平台都有自己的视图管理方式。</p>
<p>Android 上，常见的是通过 <code>ViewManager</code> 或 <code>SimpleViewManager</code> 来创建和管理原生视图。这个管理器负责把 React 传来的 props 更新到原生控件上，也负责把原生事件再发回 JavaScript。</p>
<p>iOS 上则会通过 <code>UIView</code>、<code>UIViewController</code>、属性 setter、事件回调这些方式完成同样的工作。React Native 最终要做的事情，就是把 JavaScript 侧的属性更新，映射成平台侧的对象状态变化。</p>
<p>例如一个自定义图片组件，原生侧一般会做这些事：</p>
<ol>
<li>创建一个原生图片视图</li>
<li>接收 <code>source</code>、<code>resizeMode</code>、<code>tintColor</code> 之类的属性</li>
<li>在属性变化时更新原生控件</li>
<li>在点击、加载完成、失败等节点上向 JS 发事件</li>
</ol>
<p>这样一来，JS 侧的一个 <code>&lt;Image /&gt;</code> 组件，就真正和平台图片视图接上了。</p>
<p>Android 侧的代码通常会长得像这样：</p>
<div class="language-kotlin vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">kotlin</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> MyBoxViewManager</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">SimpleViewManager</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  override</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> fun</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> getName</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "MyBox"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  override</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> fun</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> createViewInstance</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(context: </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ThemedReactContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">): </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(context)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  @ReactProp</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(name </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "color"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  fun</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setColor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(view: </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, color: </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    view.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setBackgroundColor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(color)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><p>这个例子很简单，但结构已经很完整了：创建视图、接收属性、更新原生对象。<br>
React 侧传来的 props，就是这样一点一点落到原生控件上的。</p>
<p>iOS 侧也类似，通常会创建一个 <code>UIView</code> 子类，然后通过属性更新方法把 JS 侧的值写进去，再把需要的事件回传给 JS。</p>
<h3 id="事件是怎么回来的" tabindex="-1">事件是怎么回来的 <a class="header-anchor" href="#事件是怎么回来的" aria-label="Permalink to &quot;事件是怎么回来的&quot;"></a></h3>
<p>原生侧不只是接收更新，还会主动把事件发回来。</p>
<p>比如点击、滑动、滚动、输入变化、布局完成，这些事情很多都先发生在原生侧，然后再回到 JavaScript 侧处理。常见的事件包括：</p>
<ul>
<li><code>onPress</code></li>
<li><code>onScroll</code></li>
<li><code>onChangeText</code></li>
<li><code>onLayout</code></li>
</ul>
<p>其中 <code>onLayout</code> 很值得提一下。它不是简单的“组件加载完成”，而是原生布局结果已经算出来以后回传给 JS。很多依赖尺寸的逻辑，比如弹层定位、动态高度列表、测量元素位置，都会用到这个事件。</p>
<p>如果需要主动测量一个视图，React Native 也提供了 ref 相关能力。开发者拿到一个原生组件引用以后，可以读取它的边界信息，或者在老接口里调用 <code>measure</code>。这类能力通常会用在手势定位、弹窗锚点、滚动吸顶这些场景里。</p>
<h3 id="原生模块" tabindex="-1">原生模块 <a class="header-anchor" href="#原生模块" aria-label="Permalink to &quot;原生模块&quot;"></a></h3>
<p>除了视图，React Native 还有原生模块。</p>
<p>原生模块解决的是另一类问题：有些功能本来就应该交给平台原生 API 去做，比如相机、通知、定位、文件系统、加密、数据库、传感器这些东西。</p>
<p>如果写纯 JS，这些能力要么做不到，要么性能不够好，要么对系统集成不完整。原生模块允许 JavaScript 直接调用原生代码，让这些能力以模块的方式暴露出来。</p>
<p>例如一个相机模块，JS 侧可能只是调用 <code>openCamera()</code>，真正执行的是 iOS 的 <code>AVFoundation</code> 或 Android 的相机 API。React Native 只负责把调用路由到正确的平台代码上。</p>
<p>Expo 也提供了自己的原生模块体系。Expo Modules API 允许你用 Swift 和 Kotlin 写原生能力，然后把它暴露给 JavaScript。这样做的好处是两端写法比较统一，而且和新架构兼容得更好。</p>
<p>例如一个简单的原生视图模块，在 Expo 侧可以直接声明一个原生组件，并定义属性和方法。概念上和 React Native 的 native component 很接近，只是写法更现代一些。</p>
<div class="language-swift vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">swift</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ExpoModulesCore</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> CounterModule</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Module </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  public</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> func</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> definition</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ModuleDefinition {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    Name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Counter"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    Function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"increment"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) { (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">in</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> value </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><div class="language-kotlin vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">kotlin</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> CounterModule</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> : </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Module</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  override</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> fun</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> definition</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ModuleDefinition</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    Name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Counter"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    Function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"increment"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Int</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> -></span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">      value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><p>这种模块很适合拿来做简单的原生能力验证。<br>
如果你以后要把更复杂的原生能力接进来，比如传感器、图片处理、加密算法、系统设置页入口，这种方式都是基础。</p>
<p>讲到这里，原生侧的视图、事件和模块基本都串起来了。再往下看，就能明白为什么 React Native 不能把所有事都塞到一个线程里做。</p>
<h2 id="为什么要拆线程" tabindex="-1">为什么要拆线程 <a class="header-anchor" href="#为什么要拆线程" aria-label="Permalink to &quot;为什么要拆线程&quot;"></a></h2>
<p>React Native 之所以要把这些过程拆开，一个很重要的原因，就是移动端不能把所有事情都塞到一个线程里做。</p>
<p>React Native 的线程模型里，最关键的是两个线程：</p>
<ul>
<li>UI thread，负责真正操作原生视图</li>
<li>JavaScript thread，负责执行 React 逻辑和更新计算</li>
</ul>
<p>这样拆开以后，JS 侧的计算不会直接阻塞原生界面，原生视图的更新也不会随便被 JS 打断。对于移动端来说，这一点很重要，因为用户对界面的响应速度要求很高。</p>
<p>如果所有渲染和交互都放在一个线程里，代码稍微复杂一点，界面就很容易卡住。React Native 现在的线程模型，本质上就是为了避免这个问题。</p>
<p>这里还有一个很重要的限制：原生视图只能在 UI thread 上修改。React Native 不能随意跳过这个规则，它必须遵守平台本身的线程约束。</p>
<p>线程拆开以后，问题就从“怎么画”变成了“怎么在不同层之间传递变化”。这也就是旧架构和新架构要解决的事情。</p>
<h2 id="旧架构和新架构" tabindex="-1">旧架构和新架构 <a class="header-anchor" href="#旧架构和新架构" aria-label="Permalink to &quot;旧架构和新架构&quot;"></a></h2>
<p>然后再看 React Native 的旧架构和新架构。</p>
<p>早期的 React Native 最常提到的是 Bridge。这个阶段里，JavaScript 和原生之间的通信方式更像是消息传递。JS 侧先把操作序列化，再通过桥接发给原生，原生收到以后再执行。</p>
<p>这种方式在最开始能工作，也足够清晰，但它有几个很明显的问题：</p>
<ul>
<li>序列化和反序列化会消耗性能</li>
<li>JS 和原生之间的通信不够直接</li>
<li>某些同步能力做起来比较别扭</li>
<li>调度空间有限</li>
</ul>
<p>所以 React Native 后来开始重构底层架构。新架构里比较核心的几个东西是 Fabric、JSI、TurboModules 和新的事件循环。</p>
<p>这些改动带来的变化很直接。原来的桥接方式更偏向异步消息传递，新架构则更强调 JS、C++ 和原生之间的直接协作。这样做之后，渲染链路更短，更新调度更灵活，也更容易支持现代 React 的能力。</p>
<p>这也是为什么现在再看 React Native 的渲染原理，不能只盯着 Bridge 看。Bridge 只是它曾经的一种实现方式，不是现在全部的样子。</p>
<p>讲完底层渲染和通信方式以后，Expo 的位置也就更清楚了。它不是渲染引擎，但它决定了你怎么把这些原生能力接进项目里。</p>
<h2 id="expo-在这里负责什么" tabindex="-1">Expo 在这里负责什么 <a class="header-anchor" href="#expo-在这里负责什么" aria-label="Permalink to &quot;Expo 在这里负责什么&quot;"></a></h2>
<p>说到 Expo，很多人第一反应会觉得它是不是也参与了渲染。</p>
<p>其实不是。Expo 不负责把 UI 画到屏幕上，这件事还是 React Native 自己做。Expo 主要负责的是开发流程和工程管理。</p>
<p>它管的事情包括：</p>
<ul>
<li>项目怎么初始化</li>
<li>原生工程怎么生成</li>
<li>原生模块怎么自动链接</li>
<li>开发时怎么快速预览</li>
<li>真机和模拟器怎么运行</li>
</ul>
<h3 id="expo-go" tabindex="-1">Expo Go <a class="header-anchor" href="#expo-go" aria-label="Permalink to &quot;Expo Go&quot;"></a></h3>
<p>先说 Expo Go。</p>
<p>Expo Go 可以看成一个现成的原生容器。开发者把 JavaScript bundle 连上去，就能很快看到项目效果。它很适合快速验证，但它的原生能力是固定的，不能随便加入新的原生库。</p>
<p>所以 Expo Go 更适合做早期开发和验证。它把启动成本压得很低，但也限制了你能改动的范围。</p>
<h3 id="development-build" tabindex="-1">Development Build <a class="header-anchor" href="#development-build" aria-label="Permalink to &quot;Development Build&quot;"></a></h3>
<p>如果项目需要更完整的原生能力，就需要用 development build。</p>
<p>这时候得到的不是公共容器，而是属于自己的 Expo 客户端。它可以包含自定义的原生配置、原生模块和更完整的开发能力。</p>
<p>这个阶段很重要，因为一旦项目开始依赖更多原生能力，Expo Go 就不够用了，development build 才是更接近真实产品的开发方式。</p>
<h3 id="prebuild" tabindex="-1">Prebuild <a class="header-anchor" href="#prebuild" aria-label="Permalink to &quot;Prebuild&quot;"></a></h3>
<p>Expo 的 prebuild 会根据配置生成 <code>android/</code> 和 <code>ios/</code> 目录。</p>
<p>这说明 Expo 并不是在绕开原生工程，而是在把原生工程的生成过程自动化。它降低的是维护成本，不是替代 React Native 的渲染系统。</p>
<p>如果把 React Native 看成渲染引擎，那 Expo 更像是围绕这个引擎的一整套开发工具。</p>
<p>也正因为这样，Expo 的价值不在于改写渲染过程，而在于把原生工程的复杂度提前处理掉。</p>
<h2 id="从代码到屏幕" tabindex="-1">从代码到屏幕 <a class="header-anchor" href="#从代码到屏幕" aria-label="Permalink to &quot;从代码到屏幕&quot;"></a></h2>
<p>前面把 Render、Commit、Mount、原生侧和线程模型都拆开看过一遍以后，再把它们合起来，链路就很清楚了。</p>
<p>如果把整个链路串起来，大概可以写成这样：</p>
<div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>JSX</span></span>
<span class="line"><span>  -> React Element Tree</span></span>
<span class="line"><span>  -> Shadow Tree</span></span>
<span class="line"><span>  -> Layout Calculation</span></span>
<span class="line"><span>  -> Mount to Native Views</span></span>
<span class="line"><span>  -> Screen</span></span></code></pre>
</div><p>更直接一点说就是：</p>
<p>开发者先写出界面结构，React 负责生成组件树，React Native 负责把这棵树交给布局系统和原生平台，最后由 iOS 或 Android 把结果真正显示出来。</p>
<p>所以 React Native 的目标不是做一个跨平台网页框架，而是让开发者用 React 的方式去驱动移动端原生 UI。</p>
<p>把前面的内容收回来，其实整个链路就是这件事：前端写结构，React 算树，原生平台负责把结果画出来。</p>
<p>这也正好把前面的所有概念扣在了一起。React Native 负责的是渲染，Expo 负责的是把这套渲染流程放进更好用的开发环境里。</p>
<h2 id="react-native-和-expo-的关系" tabindex="-1">React Native 和 Expo 的关系 <a class="header-anchor" href="#react-native-和-expo-的关系" aria-label="Permalink to &quot;React Native 和 Expo 的关系&quot;"></a></h2>
<p>最后再说一下 React Native 和 Expo 的关系。</p>
<p>这两个东西本来就不在同一层。</p>
<p>React Native 解决的是 UI 怎么渲染到原生平台上，Expo 解决的是开发者怎么更轻松地把项目跑起来、构建起来、维护起来。</p>
<p>把它们放在一起看，最容易混淆的地方其实也就在这里：一个管界面怎么落到屏幕上，一个管项目怎么落到设备上。</p>
<p>它们不是竞争关系，也不是替代关系，而是上下游关系：</p>
<ul>
<li>React Native 决定渲染机制</li>
<li>Expo 决定开发体验</li>
</ul>
<p>所以你用 Expo 开发 React Native 应用的时候，底层依然还是 React Native 的渲染逻辑在工作，只是 Expo 帮你把很多原生工程的细节收掉了。</p>
<p>把这两个东西放在一起看，就不会再把它们当成同一层的概念了。</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;"></a></h2>
<p>如果把前面的内容按顺序排一下，大概就是这样：</p>
<ul>
<li>React 先生成 UI 结构</li>
<li>React Native 把结构交给布局、diff 和原生挂载</li>
<li>原生平台负责把最终结果显示出来</li>
<li>Expo 负责把整个开发和构建流程管理好</li>
</ul>
<p>如果把这件事压缩成一句话，我会这么说：</p>
<p>React Native 负责把 React 的声明式 UI 变成原生视图，Expo 负责把这条链路包装成更省心的开发流程。</p>
<p>理解了这一点之后，再去看 Expo Go、development build、prebuild、Fabric、JSI 这些名词，脑子里就不会乱了。它们不是一堆孤立的术语，而是同一条链路上的不同节点。</p>
<p>前面这一套如果能说明白，后面再看别的跨端框架或者原生能力接入，思路基本也就通了。</p>
<p>这里再给下次分享挖个坑吧，下次分享大家更想了解 React 渲染网页的原理呢？还是其他 移动端 / 跨端 技术呢？还是更想了解一些后端相关知识呢？</p>
<h2 id="参考链接" tabindex="-1">参考链接 <a class="header-anchor" href="#参考链接" aria-label="Permalink to &quot;参考链接&quot;"></a></h2>
<ul>
<li><a href="https://reactnative.dev/architecture/render-pipeline" target="_blank" rel="noreferrer">React Native Render, Commit, and Mount</a></li>
<li><a href="https://reactnative.dev/architecture/threading-model" target="_blank" rel="noreferrer">React Native Threading Model</a></li>
<li><a href="https://reactnative.dev/architecture/fabric-renderer" target="_blank" rel="noreferrer">React Native Fabric</a></li>
<li><a href="https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here" target="_blank" rel="noreferrer">React Native New Architecture is here</a></li>
<li><a href="https://docs.expo.dev/develop/development-builds/introduction/" target="_blank" rel="noreferrer">Expo development builds introduction</a></li>
<li><a href="https://docs.expo.dev/develop/development-builds/use-development-builds/" target="_blank" rel="noreferrer">Expo use a development build</a></li>
<li><a href="https://docs.expo.dev/workflow/prebuild" target="_blank" rel="noreferrer">Expo Continuous Native Generation</a></li>
<li><a href="https://docs.expo.dev/guides/new-architecture/" target="_blank" rel="noreferrer">Expo React Native's New Architecture</a></li>
</ul>
]]></content:encoded>
            <author>Booling</author>
            <category>Development</category>
            <category>FrontEnd</category>
            <category>Mobile</category>
            <category>ReactNative</category>
            <category>Expo</category>
        </item>
        <item>
            <title><![CDATA[JavaScript 中的 OOP 与 FC]]></title>
            <link>https://blog.booling.cn/posts/development/oop-and-fc-in-js</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/oop-and-fc-in-js</guid>
            <pubDate>Mon, 24 Nov 2025 17:17:41 GMT</pubDate>
            <description><![CDATA[这篇分享主要让大家了解一下 js 中的 oop 与 fc，通过结合原理学习语法来提高大家的学习效率、加深大家对知识的理解。]]></description>
            <content:encoded><![CDATA[<h1 id="javascript-中的-oop-与-fc" tabindex="-1">JavaScript 中的 OOP 与 FC <a class="header-anchor" href="#javascript-中的-oop-与-fc" aria-label="Permalink to &quot;JavaScript 中的 OOP 与 FC&quot;"></a></h1>
<p>这篇分享主要让大家了解一下 js 中的 oop 与 fc，通过结合原理学习语法来提高大家的学习效率、加深大家对知识的理解。</p>
<h2 id="面向对象编程-oop" tabindex="-1">面向对象编程（OOP） <a class="header-anchor" href="#面向对象编程-oop" aria-label="Permalink to &quot;面向对象编程（OOP）&quot;"></a></h2>
<p>首先，什么是 OOP？</p>
<blockquote>
<p><strong>Object-oriented programming</strong> (<strong>OOP</strong>) is a <a href="https://en.wikipedia.org/wiki/Programming_paradigm" title="Programming paradigm" target="_blank" rel="noreferrer">programming paradigm</a> based on the <a href="https://en.wikipedia.org/wiki/Object_(computer_science)" title="Object (computer science)" target="_blank" rel="noreferrer">object</a><a href="https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-alanKayOnOO-1" target="_blank" rel="noreferrer">1</a> – a <a href="https://en.wikipedia.org/wiki/Software" title="Software" target="_blank" rel="noreferrer">software</a> entity that <a href="https://en.wikipedia.org/wiki/Encapsulation_(programming)" title="Encapsulation (programming)" target="_blank" rel="noreferrer">encapsulates</a> <a href="https://en.wikipedia.org/wiki/Data" title="Data" target="_blank" rel="noreferrer">data</a> and <a href="https://en.wikipedia.org/wiki/Function_(computer_programming)" title="Function (computer programming)" target="_blank" rel="noreferrer">function(s)</a>. An OOP <a href="https://en.wikipedia.org/wiki/Computer_program" title="Computer program" target="_blank" rel="noreferrer">computer program</a> consists of objects that interact with one another.<a href="https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-2" target="_blank" rel="noreferrer">2</a><a href="https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-3" target="_blank" rel="noreferrer">3</a> A <a href="https://en.wikipedia.org/wiki/Programming_language" title="Programming language" target="_blank" rel="noreferrer">programming language</a> that provides OOP features is classified as an <em>OOP language</em> but as the set of features that contribute to OOP is contended, classifying a language as OOP and the degree to which it supports or is OOP, are debatable. As paradigms are not mutually exclusive, a language can be <a href="https://en.wikipedia.org/wiki/Multi-paradigm" title="Multi-paradigm" target="_blank" rel="noreferrer">multi-paradigm</a>; can be categorized as more than only OOP.<br>
面向对象编程（OOP）是一种基于对象的编程范式——一个封装数据和函数的软件实体。一个面向对象的计算机程序由相互交互的对象组成。 <a href="https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-2" target="_blank" rel="noreferrer">2</a> <a href="https://en.wikipedia.org/wiki/Object-oriented_programming#cite_note-3" target="_blank" rel="noreferrer">3</a> 提供面向对象特性的编程语言被归类为面向对象语言，但由于构成面向对象特性的特征集合存在争议，将一种语言归类为面向对象语言以及它支持或是否面向对象的程度是存在争议的。由于范式不是相互排斥的，一种语言可以是多范式；可以被归类为不仅仅是面向对象。
<em>以上内容摘自 <a href="https://en.wikipedia.org/wiki/Object-oriented_programming" target="_blank" rel="noreferrer">Wikipedia</a></em></p>
</blockquote>
<p>面向对象把程序看成由 <strong>对象 <em>Object</em></strong> 组成，每个对象就像现实中的事物，有：</p>
<ul>
<li><strong>属性 <em>Attributes</em></strong>（数据）</li>
<li><strong>方法 <em>Method</em></strong>（行为、能做的事）</li>
</ul>
<p>根据对象所具有的属性与方法的不同，可以将对象进行 <strong>分类</strong>，这样就引入了 OOP 中 <strong>类 <em>Class</em></strong> 的概念。类描述了对象所具有的属性与方法，但不关心其值。</p>
<p>而根据分类方式的细致程度不同，可以将一个类里的对象进行更细致的分类，这就引入了 <strong>子类 &amp; 父类</strong> 的概念。</p>
<p>非常经典且简单的例子：
我定义 <em>动物 Animal</em> 为一个类，那么就可以细分为 <em>猫 Cat</em> 和 <em>狗 Dog</em> 等等很多个子类。Animal 就是 Cat 和 Dog 的父类。而对象就是具体某只猫，比如这只：</p>
<p><img src="https://cdn.ipfsscan.io/weibo/large/007CWdRmgy1icjimglwjvj30ho05ojsd.jpg" alt="a-object"></p>
<p>而 Animal 这一类对象都具有的 属性 和 方法 又对应什么呢？也很简单，首先属性例如：名字、年龄……方法例如：进食、呼吸……</p>
<p>Animal 的子类 Cat 和 Dog 一定具有他们父类的所有属性与方法，毕竟没有什么生物不需要进食和呼吸……</p>
<p>但特殊的地方在于：Cat 和 Dog 作为子类，可以具有父类没有的属性和方法，比如猫可以具有其特殊的方法：<strong>哈气</strong>，狗也可以有其特殊的方法：<strong>口也💩</strong>。</p>
<p>其他更加深入的内容就不再多讲了，感兴趣的同学可以私下去深入学习，或者来找我探讨。总之，OOP 的核心思想是 <strong>用对象来模拟现实世界，通过对象之间的交互完成程序功能。</strong></p>
<h2 id="函数式编程-fc" tabindex="-1">函数式编程 （FC） <a class="header-anchor" href="#函数式编程-fc" aria-label="Permalink to &quot;函数式编程 （FC）&quot;"></a></h2>
<p>那么其次，什么是函数式编程？</p>
<blockquote>
<p>In <a href="https://en.wikipedia.org/wiki/Computer_science" title="Computer science" target="_blank" rel="noreferrer">computer science</a>, <strong>functional programming</strong> is a <a href="https://en.wikipedia.org/wiki/Programming_paradigm" title="Programming paradigm" target="_blank" rel="noreferrer">programming paradigm</a> where programs are constructed by <a href="https://en.wikipedia.org/wiki/Function_application" title="Function application" target="_blank" rel="noreferrer">applying</a> and <a href="https://en.wikipedia.org/wiki/Function_composition_(computer_science)" title="Function composition (computer science)" target="_blank" rel="noreferrer">composing</a> <a href="https://en.wikipedia.org/wiki/Function_(computer_science)" title="Function (computer science)" target="_blank" rel="noreferrer">functions</a>. It is a <a href="https://en.wikipedia.org/wiki/Declarative_programming" title="Declarative programming" target="_blank" rel="noreferrer">declarative programming</a> paradigm in which function definitions are <a href="https://en.wikipedia.org/wiki/Tree_(data_structure)" title="Tree (data structure)" target="_blank" rel="noreferrer">trees</a> of <a href="https://en.wikipedia.org/wiki/Expression_(computer_science)" title="Expression (computer science)" target="_blank" rel="noreferrer">expressions</a> that map <a href="https://en.wikipedia.org/wiki/Value_(computer_science)" title="Value (computer science)" target="_blank" rel="noreferrer">values</a> to other values, rather than a sequence of <a href="https://en.wikipedia.org/wiki/Imperative_programming" title="Imperative programming" target="_blank" rel="noreferrer">imperative</a> <a href="https://en.wikipedia.org/wiki/Statement_(computer_science)" title="Statement (computer science)" target="_blank" rel="noreferrer">statements</a> which update the <a href="https://en.wikipedia.org/wiki/State_(computer_science)" title="State (computer science)" target="_blank" rel="noreferrer">running state</a> of the program.<br>
在计算机科学中，函数式编程是一种编程范式，其中程序通过应用和组合函数来构建。它是一种声明式编程范式，其中函数定义是映射值到其他值的表达式树，而不是更新程序运行状态的命令式语句序列。
<em>以上内容摘自 <a href="https://en.wikipedia.org/wiki/Functional_programming" target="_blank" rel="noreferrer">Wikipedia</a></em></p>
</blockquote>
<p>函数式编程（Functional Programming，FP）是一种把 <strong>“函数”作为核心</strong> 的编程思想，强调用<strong>表达式计算结果</strong>，而不是通过修改状态来完成任务。</p>
<p>你可以把它理解为：  <strong>用数学函数的方式写程序，让数据“流动”而不是被反复修改。</strong></p>
<p>它有几个关键特点：</p>
<ol>
<li><strong>不可变性（Immutable）</strong>：数据一旦创建就不修改，所有操作都会返回新的数据。更容易推理、减少 Bug。</li>
<li><strong>纯函数（Pure Function）</strong>：同样的输入一定得到同样输出。且不依赖外部变量、不修改外部状态。可测试性强、行为可预测。</li>
<li><strong>函数是一等公民（First-Class Citizens）</strong>：函数可以像变量一样传来传去，也能作为参数和返回值。</li>
<li><strong>高阶函数（Higher-Order Functions）</strong>：可以接收函数作为输入、或返回一个函数，例如 <code>map</code>, <code>filter</code>, <code>reduce</code>。</li>
</ol>
<p>总之，函数式编程就是用<strong>纯函数 + 不可变数据</strong>来写程序，让逻辑更简洁，状态更可控。</p>
<p>面向对象与函数式编程这两种编程思想在 JavaScript 中被巧妙地融合，并在 js 的语法中体现，接下来我们就来看看 js 中的这两种思想。</p>
<h2 id="js中的相关特性" tabindex="-1">js中的相关特性 <a class="header-anchor" href="#js中的相关特性" aria-label="Permalink to &quot;js中的相关特性&quot;"></a></h2>
<p>我们需要先对js这种语言中的各种特性有所了解，才能利用范式这种工具更好的对我们开发的软件进行组合。 这一部分主要会参考<a href="https://link.juejin.cn/?target=https%3A%2F%2Ftc39.es%2Fecma262%2F" title="https://tc39.es/ecma262/" target="_blank" rel="noreferrer">ecma262</a>。</p>
<h3 id="js语言的设计" tabindex="-1">js语言的设计 <a class="header-anchor" href="#js语言的设计" aria-label="Permalink to &quot;js语言的设计&quot;"></a></h3>
<p>Brendan Eich开发了js。他在<a href="https://link.juejin.cn/?target=https%3A%2F%2Fbrendaneich.com%2F2008%2F04%2Fpopularity%2F" title="https://brendaneich.com/2008/04/popularity/" target="_blank" rel="noreferrer">这篇文章</a>提到了js设计过程中的一些问题。<br>
Netscape需要在浏览器中内置一种脚本语言，根据领导要求，首先需要像java(Look like Java,因此很多语法和java类似),而作者本人偏向于scheme，因此最后在新的语言中选择了和Scheme一样的一等函数以及和Self一样的prototype作为主要组成。受java影响，有了原始类型和对象的区分，比如string和String。</p>
<p>以上设计加上后来的发展就成了现在的js。</p>
<h3 id="js特性概述" tabindex="-1">js特性概述 <a class="header-anchor" href="#js特性概述" aria-label="Permalink to &quot;js特性概述&quot;"></a></h3>
<p>在js中的数据结构分为两种，原始类型和对象，js的对象创建并不是基于class的，而是有很多方式，比如字面量或者构造函数，每个构造函数都有一个<code>prototype</code>属性用于实现基于原型（prototype-based）的继承。一个构造函数的<code>prototype</code>还有一个<code>constructor</code>引用指向构造函数本身，当实现继承时，这个属性可能会改，按照惯例需要修正，但不是必须的（关于constructor参考<a href="https://link.juejin.cn/?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000016147953" title="https://segmentfault.com/a/1190000016147953" target="_blank" rel="noreferrer">这里</a>）。</p>
<p>说起构造函数，这里补充一点相关概念，函数是一种特殊的对象，含有internal method <code>[[Call]]</code>,因此可以通过函数调用来执行相关代码，而构造函数又是一种特殊的函数，含有internal method <code>[[Construct]]</code>,可以通过<code>new</code>或<code>super</code>调用创建对象。</p>
<h3 id="js中的对象" tabindex="-1">js中的对象 <a class="header-anchor" href="#js中的对象" aria-label="Permalink to &quot;js中的对象&quot;"></a></h3>
<h4 id="原型链" tabindex="-1">原型链 <a class="header-anchor" href="#原型链" aria-label="Permalink to &quot;原型链&quot;"></a></h4>
<p>每一个通过构造函数创建的对象都有一个指向构造函数prototype属性的隐式引用(可以用<code>__proto__</code>访问但不推荐)，而这个prototype本身可能也有一个非null的引用指向它的prototype,等等，这就被称为原型链。当访问对象的一个属性时，会首先从该对象本身查找，如果找不到就会沿着原型链依次查找，直到找到或者找到尽头发现没有，原型链上的属性可以被覆盖。</p>
<p>相较基于class继承的语言，通常来说，状态被实例拥有，方法被class拥有，继承的只有结构和行为(behavior)，而js这一切都是可以继承的。这里的<a href="https://link.juejin.cn/?target=https%3A%2F%2Fwww.informit.com%2Farticles%2Farticle.aspx%3Fp%3D25856%26seqNum%3D6%23%3A~%3Atext%3DThe%2520behavior%2520of%2520an%2520object%2Cdefined%2520within%2520the%2520object%2520class.%26text%3DMethods%2520determine%2520what%2520type%2520of%2Cdata%252C%2520and%2520its%2520overall%2520behavior." title="https://www.informit.com/articles/article.aspx?p=25856&amp;seqNum=6#:~:text=The%20behavior%20of%20an%20object,defined%20within%20the%20object%20class.&amp;text=Methods%20determine%20what%20type%20of,data%2C%20and%20its%20overall%20behavior." target="_blank" rel="noreferrer">行为是方法整体决定的，如果没有方法，一个class将只有结构</a>。</p>
<p>原型链的尽头为null，要明确一个对象的原型链到底包括什么，这里可以大概分为以下</p>
<ul>
<li>如果是个<code>new Object()</code>或字面量<code>{}</code>,其原型链为</li>
</ul>
<div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">var </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">obj</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">={}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">//原型链 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">obj</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=></span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">Object.prototype</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=>null</span></span></code></pre>
</div><ul>
<li>如果是new调用了其他构造函数，包括自定义的或者内置的（比如Array，内置构造函数不一定需要new调用，比如也可以通过字面量或者不使用new，比如<code>[]</code>或<code>Date()</code>）,这里以Array为例</li>
</ul>
<div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> arr =[]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">//arr=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Array.prototype=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.prototype=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">null</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">//如果是自定义构造函数也一样</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> P=function(){}</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> p=new P()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">//p=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">P</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.prototype=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.prototype=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">null</span></span></code></pre>
</div><ul>
<li>如果在上一种情况下,延长原型链,其实就是怎么样实现继承</li>
</ul>
<div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">//1. 使用Object.create()</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> q=</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.create(</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">// q=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">//2. 直接修改构造函数的prototype</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">function Q(){}</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">Q</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.prototype=</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> q=new Q()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">//q=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">//3. 通过Object.setPrototypeOf(obj, prototype)设置__proto__属性,可以直接修改原型链，这个操作很浪费性能，少用</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">function P(){</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">function Q(){</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=2</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> q=new Q()</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.setPrototypeOf(</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">q</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">P</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">.prototype</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">// q=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">P</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">.prototype</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">//4. 使用call和apply借用构造函数时，和原型无关</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> P=function(v){</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">v</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">function Q(v){</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    P</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">call</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">v</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> q=new Q(2)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">// q=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">Q</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.prototype=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.prototype=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">null</span></span></code></pre>
</div><p>我们可以通过<code>object instanceof constructor</code> 判断一个构造函数的prototype是否在指定对象的原型链中</p>
<div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Q</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> q</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Q</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(q </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">instanceof</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Q</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(q </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">instanceof</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
</div><p>可以通过<code>Object.getPrototypeOf(object)</code>获得对象的<code>__proto__</code>属性</p>
<h4 id="object" tabindex="-1">Object <a class="header-anchor" href="#object" aria-label="Permalink to &quot;Object&quot;"></a></h4>
<p>Object.prototype上有一些属性和方法被其他所有对象继承，在特定对象继承过程中可能会对某些字段重写。<br>
另外Object上还有很多静态方法用于处理关于对象的各种操作，具体请参考<a href="https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FObject" title="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object" target="_blank" rel="noreferrer">mdn</a></p>
<h3 id="js中的函数" tabindex="-1">js中的函数 <a class="header-anchor" href="#js中的函数" aria-label="Permalink to &quot;js中的函数&quot;"></a></h3>
<p>在js中所有函数都是Function的实例，包括Object和Function本身,乃至各种内置构造函数（比如Array）,因此有</p>
<div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Function.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">__proto__</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">prototype</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//true</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">prototype</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">__proto__</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Object</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">prototype</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//true,即Function instanceof Object</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//原型链 Function=>Function.prototype=>Object.prototype,以下类似</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Object.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">__proto__</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">prototype</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//true，即Object instanceof Function</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">Array.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">__proto__</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">prototype</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> //true</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(){}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">a.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">__proto__</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">prototype</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//true</span></span></code></pre>
</div><p>可见，所有函数的原型链到达<code>Object.prototype</code>之前需要先经过<code>Function.prototype</code>,一个函数是一个对象，更是一个函数。</p>
<h4 id="function" tabindex="-1">Function <a class="header-anchor" href="#function" aria-label="Permalink to &quot;Function&quot;"></a></h4>
<p>Funciton.prototype上有一些方法值得我们关注</p>
<ul>
<li>func.apply(thisArg, [argsArray])</li>
<li>function.call(thisArg, arg1, arg2, ...)</li>
<li>function.bind(thisArg[, arg1[, arg2[, ...]]])</li>
</ul>
<p>其中前两个在一个对象的上下文应用另一个对象的方法，第三个用于修改上下文，其余参数会在返回的函数调用时使用</p>
<h2 id="js的函数式编程" tabindex="-1">js的函数式编程 <a class="header-anchor" href="#js的函数式编程" aria-label="Permalink to &quot;js的函数式编程&quot;"></a></h2>
<p>在具体的了解函数式编程之前，这里先了解一些概念，参考Composing Software。</p>
<h3 id="概念" tabindex="-1">概念 <a class="header-anchor" href="#概念" aria-label="Permalink to &quot;概念&quot;"></a></h3>
<h4 id="pure-function" tabindex="-1">Pure Function <a class="header-anchor" href="#pure-function" aria-label="Permalink to &quot;Pure Function&quot;"></a></h4>
<p>一个纯函数是一个函数，符合以下特点</p>
<ul>
<li>相同的输入总是返回相同输出</li>
<li>没有副作用</li>
</ul>
<p>纯函数在函数式编程中很重要，但是实际的开发中，函数或多或少会有一些副作用，比如数据获取和操作dom。</p>
<h4 id="function-composition" tabindex="-1">Function Composition <a class="header-anchor" href="#function-composition" aria-label="Permalink to &quot;Function Composition&quot;"></a></h4>
<p>函数复合是将两个或多个函数按照顺序生成一个函数或者执行操作。</p>
<h4 id="shared-state" tabindex="-1">Shared State <a class="header-anchor" href="#shared-state" aria-label="Permalink to &quot;Shared State&quot;"></a></h4>
<p>共享数据可以是变量、对象或内存空间。使用共享数据的一个问题是为了了解一个函数的副作用，需要知道每个共享数据的操作历史，比如对一个用户信息在不同终端的修改会发生冲突，因此在flux中要使用单向流。<br>
另一个问题是对共享数据的操作顺序也会造成不同结果，比如四则运算。</p>
<h4 id="immutability" tabindex="-1">Immutability <a class="header-anchor" href="#immutability" aria-label="Permalink to &quot;Immutability&quot;"></a></h4>
<p>一个不可变对象是创建后就不能改变，但是js在语言层面只提供了原始类型的不可变性，对对象并不提供这种特性，即使使用<code>Object.freeze()</code>等方法也只能冻结某个层级的对象修改，要想使用不可变数据，可以使用第三方库，比如<a href="https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fimmerjs%2Fimmer" title="https://github.com/immerjs/immer" target="_blank" rel="noreferrer">Immer</a>。<br>
不可变对象是函数式编程的核心概念，没有不可变性，程序中的数据流就会不可控，应该使用原数据生成新数据，而不应该修改原来的数据。</p>
<p>在实际的操作中，对于一个特定的数据，不可变性和不同享，至少要满足一个。</p>
<h4 id="side-effects" tabindex="-1">Side Effects <a class="header-anchor" href="#side-effects" aria-label="Permalink to &quot;Side Effects&quot;"></a></h4>
<p>副作用指的是除了对输出结果操作以外其他的操作，比如打印日志或修改dom，副作用在函数式编程中应该避免，即将副作用和数据流处理分开。</p>
<h4 id="reusability-through-higher-order-functions" tabindex="-1">Reusability Through Higher Order Functions <a class="header-anchor" href="#reusability-through-higher-order-functions" aria-label="Permalink to &quot;Reusability Through Higher Order Functions&quot;"></a></h4>
<p>高阶函数是任何以函数作为参数或返回函数的函数，经常用于</p>
<ul>
<li>使用回调函数、promise或monads对动作、副作用或异步数据流进行抽象或隔离。</li>
<li>为操作各种类型的变量创建工具函数</li>
<li>为了复用或函数组合而创建偏函数或柯里化</li>
<li>将一系列输入的函数串联返回一个函数组合</li>
</ul>
<h4 id="containers-functors-lists-and-streams" tabindex="-1">Containers, Functors, Lists, and Streams <a class="header-anchor" href="#containers-functors-lists-and-streams" aria-label="Permalink to &quot;Containers, Functors, Lists, and Streams&quot;"></a></h4>
<p>这里包括上面提到的monads，可以参考<a href="https://link.juejin.cn/?target=https%3A%2F%2Fadit.io%2Fposts%2F2013-04-17-functors%2C_applicatives%2C_and_monads_in_pictures.html" title="https://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html" target="_blank" rel="noreferrer">Functors, Applicatives, And Monads In Pictures</a></p>
<p>一个functor数据结构可以用于映射数据，比如 <code>[1,2,3].map(x =&gt; x *2)</code>,换句话说，它是一个容器，会为内部的数据应用一个函数，当看到这个词时应该想到<code>mappable</code><br>
在这里被映射的是一个数组，只要提供map api，其他数据结构应该也可以，一个按顺序处理的list可以看作是一个stream。</p>
<h4 id="declarative-vs-imperative" tabindex="-1">Declarative vs Imperative <a class="header-anchor" href="#declarative-vs-imperative" aria-label="Permalink to &quot;Declarative vs Imperative&quot;"></a></h4>
<p>函数式编程是一种声明式范式，声明式编程会将流控制过程抽象，而不是用一行行代码描述怎么做，对应的是命令式。 比如函数doubleMap命令式的写法</p>
<div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">const </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">doubleMap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> = </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">numbers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> => {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">const </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">doubled</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> = []</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">for (let </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> = 0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">; i &#x3C; numbers.length; i++) {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	doubled.push(numbers[i] * 2)</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">   }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> return doubled</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> console.log(doubleMap([2, 3, 4]))</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">;</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // [4, 6, 8]</span></span></code></pre>
</div><p>声明式的写法</p>
<div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">const </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">doubleMap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> = </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">numbers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> => numbers.map(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">n</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> => n * 2)</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> console.log(doubleMap([2, 3, 4]))</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">;</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // [4, 6, 8]</span></span></code></pre>
</div><h3 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;"></a></h3>
<p>一个函数式编程应该有以下特征</p>
<ul>
<li>纯函数，而不是共享数据和副作用</li>
<li>不可变，而不是可变数据</li>
<li>函数组合，而不是命令式流控制</li>
<li>泛型工具而不是对某些数据的特定方法</li>
<li>声明式，而不是命令式</li>
<li>表达式，而不是语句</li>
</ul>
<p>在js中的函数式应用可以参考<a href="https://link.juejin.cn/?target=https%3A%2F%2Fjrsinclair.com%2Farticles%2F2016%2Fgentle-introduction-to-functional-javascript-intro%2F" title="https://jrsinclair.com/articles/2016/gentle-introduction-to-functional-javascript-intro/" target="_blank" rel="noreferrer">A GENTLE INTRODUCTION TO FUNCTIONAL JAVASCRIPT</a>系列，最终目的就是将应用中的整个逻辑切分到不同的函数中，然后将函数组合，完成最终的任务。<br>
在具体处理过程中注意函数式编程的各种特征。</p>
<p>常见的函数组合方式包括</p>
<ul>
<li>compose,又称为pipe</li>
</ul>
<div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">　const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> compose</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">functions</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> flowIn</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> functions.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">reduceRight</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">( ( </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">acc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">f</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> f</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(acc), flowIn )</span></span></code></pre>
</div><ul>
<li>curry,这里实现一个具体的柯里化</li>
</ul>
<div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">const add = </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D"> a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> +</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">add(1)(2)</span></span></code></pre>
</div><h2 id="js的面向对象编程" tabindex="-1">js的面向对象编程 <a class="header-anchor" href="#js的面向对象编程" aria-label="Permalink to &quot;js的面向对象编程&quot;"></a></h2>
<p>js的以原型为基础的继承不太适合实现面向对象的封装、继承和多态，而es6在语言层面实现了class语法，可以很方便的采用其他语言实践总结而来的设计模式和设计原则，这里建议采用ts,具体可参考<a href="https://juejin.cn/post/6897620357885198344" title="https://juejin.cn/post/6897620357885198344" target="_blank" rel="noreferrer">ts实现的23种设计模式和设计原则</a></p>
<p>无论采用何种范式，最终都是要将各个模块组合成我们的软件。</p>
<h1 id="参考链接" tabindex="-1">参考链接 <a class="header-anchor" href="#参考链接" aria-label="Permalink to &quot;参考链接&quot;"></a></h1>
<p>本篇博客中部分内容摘自以下博文：</p>
<ul>
<li><a href="https://juejin.cn/post/6926930448283074568" target="_blank" rel="noreferrer">说起编程范式我们应知道什么</a></li>
<li><a href="./https_juejin.cn/?url=https%3A%2F%2Fjuejin.cn%2Fpost%2F6932852475527430157%23heading-6">js中的面向对象编程和函数式编程</a></li>
</ul>
]]></content:encoded>
            <author>Booling</author>
            <category>Development</category>
            <category>FrontEnd</category>
            <category>Study</category>
            <enclosure url="https://cdn.ipfsscan.io/weibo/large/007CWdRmgy1icjimglwjvj30ho05ojsd.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Storybook + Vite on Bun]]></title>
            <link>https://blog.booling.cn/posts/development/storybook-init</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/storybook-init</guid>
            <pubDate>Sun, 06 Apr 2025 16:53:49 GMT</pubDate>
            <description><![CDATA[记录一下在 Bun 上将 Storybook 集成到 Vite 组件库项目中的过程]]></description>
            <content:encoded><![CDATA[<h1 id="storybook-集成" tabindex="-1">Storybook 集成 <a class="header-anchor" href="#storybook-集成" aria-label="Permalink to &quot;Storybook 集成&quot;"></a></h1>
<h2 id="什么是-storybook" tabindex="-1">什么是 Storybook <a class="header-anchor" href="#什么是-storybook" aria-label="Permalink to &quot;什么是 Storybook&quot;"></a></h2>
<p>Storybook 是一个开源工具，用于独立构建、测试和文档化 UI 组件。它为开发者提供了一个独立于主应用程序的环境，使组件开发更加高效和有条理。</p>
<h2 id="主要特点" tabindex="-1">主要特点 <a class="header-anchor" href="#主要特点" aria-label="Permalink to &quot;主要特点&quot;"></a></h2>
<ul>
<li><strong>隔离环境</strong>：在应用程序上下文之外开发 UI 组件</li>
<li><strong>组件驱动开发</strong>：专注于一次构建一个组件</li>
<li><strong>可视化测试</strong>：直观地检查组件在不同状态下的外观</li>
<li><strong>交互式文档</strong>：创建活跃的、可交互的组件文档</li>
<li><strong>插件生态系统</strong>：通过众多插件扩展功能</li>
</ul>
<h2 id="使用场景" tabindex="-1">使用场景 <a class="header-anchor" href="#使用场景" aria-label="Permalink to &quot;使用场景&quot;"></a></h2>
<ul>
<li>创建设计系统或组件库</li>
<li>开发复杂的 UI 界面</li>
<li>测试组件的边缘情况</li>
<li>协作开发（设计师和开发者）</li>
</ul>
<h2 id="项目集成" tabindex="-1">项目集成 <a class="header-anchor" href="#项目集成" aria-label="Permalink to &quot;项目集成&quot;"></a></h2>
<p>Storybook 的 cli 貌似不太好用，这边提供一个将 Storybook 与 Vite 项目集成的方案。</p>
<h3 id="环境要求" tabindex="-1">环境要求 <a class="header-anchor" href="#环境要求" aria-label="Permalink to &quot;环境要求&quot;"></a></h3>
<ul>
<li>bun</li>
<li>React ≥ 16.8</li>
<li>Vite ≥ 4.0</li>
<li>Storybook ≥ 8.0</li>
</ul>
<h3 id="创建项目" tabindex="-1">创建项目 <a class="header-anchor" href="#创建项目" aria-label="Permalink to &quot;创建项目&quot;"></a></h3>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bun</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> create</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vite</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">reused</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 0,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> downloaded</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> added</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 1,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> done</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  Project</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> name:</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  storybook-demo</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  Select</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> a</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> framework:</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  React</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  Select</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> a</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> variant:</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  TypeScript</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> SWC</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">o</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  Scaffolding</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> project</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> in</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> D:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\P</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">rojects</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">torybook-demo...</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">—</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  Done.</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Now</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run:</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> storybook-demo</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  bun</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  bun</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dev</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bun</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">...</span></span></code></pre>
</div><p>项目结构：</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tree</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> README.md</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eslint.config.js</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index.html</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> package.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> public</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vite.svg</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> src</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> App.css</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> App.tsx</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> assets</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> react.svg</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index.css</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> main.tsx</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vite-env.d.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.app.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.node.json</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vite.config.ts</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">3</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> directories,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 15</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> files</span></span></code></pre>
</div><p>vite create 默认没有创建库项目的选项，下面我们将这个项目改造成库项目。</p>
<p>先删掉 <code>src/</code> 下的 css、ts 和资源文件，创建一个空的 <code>index.ts</code> 文件，现在项目结构长这样：</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tree</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> README.md</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eslint.config.js</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index.html</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> package.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> src</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vite-env.d.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.app.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.node.json</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vite.config.ts</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">2</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> directories,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 11</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> files</span></span></code></pre>
</div><p>修改 <code>vite.config.ts</code> 文件，将项目类型改为库项目：</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { defineConfig } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'vite'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> react </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '@vitejs/plugin-react-swc'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// https://vite.dev/config/</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> defineConfig</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  plugins: [</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">react</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()],</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 新增以下配置</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  build: {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    lib: {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      entry: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'src/index.ts'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      name: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'my-components'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      formats: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'es'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
</div><p>记得安装一下 sass 依赖，我们接下来要使用 scss 编写样式：</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bun</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -D</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> sass-embedded</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Packages:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> +20</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">++++++++++++++++++++</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Progress:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> resolved</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 483,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> reused</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 403,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> downloaded</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 7,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> added</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 20,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> done</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">devDependencies:</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">+</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> sass-embedded</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1.86.3</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Done</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> in</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 7.2s</span></span></code></pre>
</div><p>现在就是一个 React 库项目咯，可以开始集成 Storybook 了。</p>
<h3 id="集成-storybook" tabindex="-1">集成 Storybook <a class="header-anchor" href="#集成-storybook" aria-label="Permalink to &quot;集成 Storybook&quot;"></a></h3>
<p>安装 Storybook：</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">//</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 安装</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Storybook</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 时会调用</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 安装一些包，如果</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 环境有问题可以加上</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --package-manager=bun</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bun</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> create</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> storybook@latest</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">╭───────────────────────────────────────────────────────╮</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                       │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Adding</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Storybook</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> version</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 8.6.12</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> your</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> project..</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                       │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">╰───────────────────────────────────────────────────────╯</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">√</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> What</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> do</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> you</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> want</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> use</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Storybook</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> for?</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> »</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Documentation:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> MDX,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> auto-generated</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> component</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> docs,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Testing:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Fast</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> browser-based</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> component</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tests,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> watch</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> mode</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> •</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Detecting</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> project</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> type.</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ✓</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Installing</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dependencies...</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Lockfile</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> is</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> date,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> resolution</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> step</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> is</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> skipped</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Already</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> date</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Done</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> in</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 678ms</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> •</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Adding</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Storybook</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> support</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> your</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "React"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> app</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> •</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Detected</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Vite</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> project.</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Setting</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> builder</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Vite.</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ✓</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  ✅</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Getting</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> correct</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> version</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> of</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> packages</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">.</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ✓</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Installing</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> dependencies...</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Lockfile</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> is</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> date,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> resolution</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> step</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> is</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> skipped</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Already</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> date</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Done</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> in</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 750ms</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> npx storybook@8.6.12 add @storybook/experimental-addon-test@8.6.12</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Verifying</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> @storybook/experimental-addon-test</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Installing</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> @storybook/experimental-addon-test@^8.6.12</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Adding</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> '@storybook/experimental-addon-test@8.6.12'</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "addons"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> field</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> in</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> .storybook</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\m</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ain.ts</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Running</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> postinstall</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> script</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> for</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> @storybook/experimental-addon-test</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">╭</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 👋</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Howdy!</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ─────────────────────────────────────────────────────────────────╮</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                                            │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   I</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\'</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">m</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> installation</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> helper</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> for</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> @storybook/experimental-addon-test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">       │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                                            │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Hold</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> on</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> for</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> a</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> moment</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> while</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> I</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> look</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> at</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> your</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> project</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> and</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> get</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> it</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> set</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> up...</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                                            │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">╰────────────────────────────────────────────────────────────────────────────╯</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">›</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Configuring</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Playwright</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> with</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Chromium</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (this </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">might</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> take</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> some</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> time</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">):</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> playwright</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> chromium</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --with-deps</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">╭</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 🚨</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Oh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> no!</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ──────────────────────────────────────────────────────────────╮</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                                         │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Found</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> an</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> existing</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Vitest</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> setup</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> file:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                  │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   D:/Projects/storybook-demo/.storybook/vitest.setup.ts</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                 │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                                         │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Please</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> refer</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> documentation</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> complete</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> the</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> setup</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> manually:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">     │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   https://storybook.js.org/docs/writing-tests/test-addon#manual-setup</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                                         │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">╰─────────────────────────────────────────────────────────────────────────╯</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">╭──────────────────────────────────────────────────────────────────────────────╮</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                                              │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Storybook</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> was</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> successfully</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> installed</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> in</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> your</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> project!</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 🎉</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                   │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Additional</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> features:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Documentation,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Testing</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                                              │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   To</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Storybook</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> manually,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bun</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> storybook.</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> CTRL+C</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stop.</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">         │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                                              │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Wanna</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> know</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> more</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> about</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Storybook?</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Check</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> out</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://storybook.js.org/</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">       │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   Having</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> trouble</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> or</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> want</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> to</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> chat?</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Join</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> us</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> at</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> https://discord.gg/storybook/</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">│</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">                                                                              │</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">╰──────────────────────────────────────────────────────────────────────────────╯</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Running</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Storybook</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> storybook-demo@0.0.0 storybook D:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\P</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">rojects</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\s</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">torybook-demo</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> storybook dev -p 6006 </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"--initial-path=/onboarding"</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "--quiet"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">@storybook/core</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> v8.6.12</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">info</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Serving</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> static</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> files</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> D:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\P</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">rojects</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">torybook-demo</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">ode_modules</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\.</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">cache</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\s</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">torybook</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">efault</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">\c</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">overage</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> at</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /coverage</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">info</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Using</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> paths</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> for</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> react-docgen</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">17:58:40</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [vite] (</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">client</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) hmr update /@id/__x00__virtual:/@storybook/builder-vite/vite-app.js</span></span></code></pre>
</div><p>这里会自动打开 Storybook 的欢迎页面，按照指引操作即可。（注意让你命名新 stories 时不要和已有的 stories 重名）</p>
<p>完成上述操作以后的项目结构如下：</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tree</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -F</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -I</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "node_modules"</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> README.md</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eslint.config.js</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index.html</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> package.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bun-lock.yaml</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> src/</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> stories/</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button.stories.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button.tsx</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Configure.mdx</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Header.stories.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Header.tsx</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Page.stories.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Page.tsx</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> assets/</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> accessibility.png</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> accessibility.svg</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> addon-library.png</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> assets.png</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> avif-test-image.avif</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> context.png</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> discord.svg</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> docs.png</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> figma-plugin.png</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> github.svg</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> share.png</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> styling.png</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> testing.png</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> theming.png</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tutorials.svg</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> youtube.svg</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> button.css</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> header.css</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> page.css</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vite-env.d.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.app.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.node.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vite.config.ts</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vitest.workspace.ts</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">3</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> directories,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 38</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> files</span></span></code></pre>
</div><p>我们先暂时忽略一下 <code>src/stories/</code> 目录，在 <code>src/components</code> 中创建 <code>Button</code> 文件夹并添加如下几个文件：</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tree</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> ./src/components/Button/</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">./src/components/Button/</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button.scss</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button.stories.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button.test.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button.tsx</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index.ts</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">0</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> directories, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">5</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> files</span></span></code></pre>
</div><p><code>Button.tsx</code> 文件可以直接复制 <code>stories/Button.tsx</code> 的内容，只要将首行的 import 语句导入的样式文件后缀名改成 <code>scss</code> 即可，<code>Button.stories.ts</code> 文件可以直接复制 <code>stories/Button.stories.ts</code> 的内容，<code>Button.scss</code> 文件也可以直接复制 <code>stories/Button.css</code> 的内容，因为 scss 是 css 的超集，可以无缝转换 ，<code>Button.test.ts</code> 文件暂时先不管，这里是组件测试的代码，笔者还没研究。</p>
<p>现在在 <code>src\Button\index.ts</code> 中导出组件：</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { Button } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> './Button'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Button;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> type</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { ButtonProps } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> './Button'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span></code></pre>
</div><p>在 <code>src/index.ts</code> 中导出组件：</p>
<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">default</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> as</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Button } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> './components/Button'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span></code></pre>
</div><p>现在可以把 <code>src/stories</code> 目录删掉啦，需要抄的代码已经抄完了，可以不用它了。删掉之前可以先看一看 <code>src\stories\Configure.mdx</code> 的内容，学习一下怎么使用 MDX 书写组件库文档。</p>
<p>完成后的项目结构：</p>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">$</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tree</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -F</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -I</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "node_modules"</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">.</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> README.md</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eslint.config.js</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index.html</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> package.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bun-lock.yaml</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> src/</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> components/</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button/</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">       |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button.scss</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">       |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button.stories.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">       |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button.test.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">       |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> Button.tsx</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">       `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">   |</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> index.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">   `</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vite-env.d.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.app.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> tsconfig.node.json</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vite.config.ts</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">--</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> vitest.workspace.ts</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">3</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> directories,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 17</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> files</span></span></code></pre>
</div><p>现在就已经可以愉快的书写组件库啦😋，接下来我们来看看 Storybook 的使用。</p>
<h3 id="storybook-使用" tabindex="-1">Storybook 使用 <a class="header-anchor" href="#storybook-使用" aria-label="Permalink to &quot;Storybook 使用&quot;"></a></h3>
<p>在重构项目结构以后，打开 <code>package.json</code>，可以看到现在的脚本如下：</p>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">  ...,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "scripts"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "dev"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"vite"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "build"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tsc -b &#x26;&#x26; vite build"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "lint"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"eslint ."</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "preview"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"vite preview"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "storybook"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"storybook dev -p 6006"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "build-storybook"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"storybook build"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">  ...</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><p>由于现在的项目是一个库项目，无法直接使用 <code>vite</code> 命令运行，所以我们可以稍作修改：</p>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">  ...,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "scripts"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "dev"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"storybook dev -p 6006"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "lint"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"eslint ."</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "build"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"bun run build:js &#x26;&#x26; bun run build:types &#x26;&#x26; bun run build:css"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "build:js"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"vite build"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "build:types"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tsc --emitDeclarationOnly"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "build:storybook"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"storybook build"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">  ...</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><p>现在运行 <code>bun dev</code> 就可以直接启动 Storybook 开发服务器啦，快去试试吧🎉</p>
]]></content:encoded>
            <author>Booling</author>
            <category>Development</category>
            <category>FrontEnd</category>
            <category>Study</category>
        </item>
        <item>
            <title><![CDATA[Lottie 在 Web 开发中的应用]]></title>
            <link>https://blog.booling.cn/posts/development/lottie-in-web</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/lottie-in-web</guid>
            <pubDate>Sun, 06 Apr 2025 00:44:14 GMT</pubDate>
            <description><![CDATA[稍微调研了一些 Lottie Web 库，这里记录一下]]></description>
            <content:encoded><![CDATA[<h1 id="lottie-在-web-开发中的应用" tabindex="-1">Lottie 在 Web 开发中的应用 <a class="header-anchor" href="#lottie-在-web-开发中的应用" aria-label="Permalink to &quot;Lottie 在 Web 开发中的应用&quot;"></a></h1>
<p><a href="https://airbnb.design/lottie/" target="_blank" rel="noreferrer">Lottie</a> 是由 Airbnb 开源的一套跨平台动画解决方案。它极大地简化了设计师和开发者之间的协作流程 - 设计师可以在 After Effects (AE) 或 Figma 等设计软件中创建动画，导出为 JSON 格式后，开发者便可以在各个平台上实现一致的动画效果。</p>
<p>作为前端开发者，Lottie 的出现显著降低了实现复杂动画的门槛。无需手写复杂的 CSS 动画或 Canvas 绘制代码，通过简单的配置就能完美还原设计稿中的动画效果。</p>
<h2 id="设计工作流" tabindex="-1">设计工作流 <a class="header-anchor" href="#设计工作流" aria-label="Permalink to &quot;设计工作流&quot;"></a></h2>
<h3 id="figma-工作流" tabindex="-1">Figma 工作流 <a class="header-anchor" href="#figma-工作流" aria-label="Permalink to &quot;Figma 工作流&quot;"></a></h3>
<ol>
<li>安装 <a href="https://www.figma.com/community/plugin/809860933081065308/lottiefiles" target="_blank" rel="noreferrer">LottieFiles for Figma</a> 插件</li>
<li>使用 Figma 创建动画</li>
<li>通过插件导出为 JSON 格式</li>
</ol>
<h3 id="after-effects-工作流" tabindex="-1">After Effects 工作流 <a class="header-anchor" href="#after-effects-工作流" aria-label="Permalink to &quot;After Effects 工作流&quot;"></a></h3>
<ol>
<li>安装 <a href="https://lottiefiles.com/plugins/after-effects" target="_blank" rel="noreferrer">LottieFiles for After Effects</a> 插件</li>
<li>使用 AE 制作动画</li>
<li>通过插件导出为 JSON 格式</li>
</ol>
<h2 id="web-端实现方案对比" tabindex="-1">Web 端实现方案对比 <a class="header-anchor" href="#web-端实现方案对比" aria-label="Permalink to &quot;Web 端实现方案对比&quot;"></a></h2>
<p>在前端开发中，主要有三种实现方案：</p>
<h3 id="_1-lottie-web-官方-sdk" tabindex="-1">1. Lottie Web (官方 SDK) <a class="header-anchor" href="#_1-lottie-web-官方-sdk" aria-label="Permalink to &quot;1. Lottie Web (官方 SDK)&quot;"></a></h3>
<ul>
<li><strong>优点</strong>：
<ul>
<li>官方维护，更新及时</li>
<li>性能优化，直接使用 Canvas/SVG 渲染</li>
<li>提供完整的动画控制 API</li>
</ul>
</li>
<li><strong>缺点</strong>：
<ul>
<li>需要手动处理与框架的集成</li>
<li>API 偏底层，使用相对复杂</li>
</ul>
</li>
</ul>
<h3 id="_2-react-lottie" tabindex="-1">2. React Lottie <a class="header-anchor" href="#_2-react-lottie" aria-label="Permalink to &quot;2. React Lottie&quot;"></a></h3>
<ul>
<li><strong>优点</strong>：
<ul>
<li>专为 React 设计的封装</li>
<li>API 简单易用</li>
</ul>
</li>
<li><strong>缺点</strong>：
<ul>
<li>基于较早期的 bodymovin.js</li>
<li>使用 Class Components，不完全适配现代 React</li>
<li>TypeScript 支持有限</li>
</ul>
</li>
</ul>
<h3 id="_3-lottie-react" tabindex="-1">3. Lottie React <a class="header-anchor" href="#_3-lottie-react" aria-label="Permalink to &quot;3. Lottie React&quot;"></a></h3>
<ul>
<li><strong>优点</strong>：
<ul>
<li>基于最新的 Lottie Web</li>
<li>完整的 TypeScript 支持</li>
<li>支持函数组件和 Hooks</li>
<li>提供声明式和命令式两种使用方式</li>
</ul>
</li>
<li><strong>推荐使用场景</strong>：
<ul>
<li>现代 React 项目</li>
<li>需要 TypeScript 支持</li>
<li>简单动画展示需求</li>
</ul>
</li>
</ul>
<h2 id="实战应用" tabindex="-1">实战应用 <a class="header-anchor" href="#实战应用" aria-label="Permalink to &quot;实战应用&quot;"></a></h2>
<h3 id="lottie-web-使用示例" tabindex="-1">Lottie Web 使用示例 <a class="header-anchor" href="#lottie-web-使用示例" aria-label="Permalink to &quot;Lottie Web 使用示例&quot;"></a></h3>
<h4 id="安装" tabindex="-1">安装 <a class="header-anchor" href="#安装" aria-label="Permalink to &quot;安装&quot;"></a></h4>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># NPM</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> lottie-web</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 或使用 CDN</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&#x3C;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">script src</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.7.4/lottie.min.js"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>&#x3C;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">/script</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span></span></code></pre>
</div><h4 id="基础使用" tabindex="-1">基础使用 <a class="header-anchor" href="#基础使用" aria-label="Permalink to &quot;基础使用&quot;"></a></h4>
<div class="language-html vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"lottie-container"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> style</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"width: 400px; height: 400px"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">script</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> animation</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lottie.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">loadAnimation</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    container: document.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getElementById</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'lottie-container'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    renderer: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'svg'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 可选: 'svg' | 'canvas' | 'html'</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    loop: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    autoplay: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    path: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'animation.json'</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 动画文件路径</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  });</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">script</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
</div><h4 id="react-集成示例" tabindex="-1">React 集成示例 <a class="header-anchor" href="#react-集成示例" aria-label="Permalink to &quot;React 集成示例&quot;"></a></h4>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { useEffect, useRef } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'react'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lottie, { AnimationItem } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'lottie-web'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> animationData </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> './animation.json'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> LottieAnimation</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> containerRef</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useRef</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">HTMLDivElement</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> animationRef</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useRef</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">AnimationItem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  useEffect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">containerRef.current) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    animationRef.current </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lottie.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">loadAnimation</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      container: containerRef.current,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      renderer: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'svg'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      loop: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      autoplay: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      animationData,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    });</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> animationRef.current?.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">destroy</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }, []);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ref</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{containerRef} </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">style</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{{ width: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">400</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, height: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">400</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }} />;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> LottieAnimation;</span></span></code></pre>
</div><h3 id="lottie-react-使用示例" tabindex="-1">Lottie React 使用示例 <a class="header-anchor" href="#lottie-react-使用示例" aria-label="Permalink to &quot;Lottie React 使用示例&quot;"></a></h3>
<h4 id="安装-1" tabindex="-1">安装 <a class="header-anchor" href="#安装-1" aria-label="Permalink to &quot;安装&quot;"></a></h4>
<div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> lottie-react</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 或</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">pnpm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> lottie-react</span></span></code></pre>
</div><h4 id="组件式使用" tabindex="-1">组件式使用 <a class="header-anchor" href="#组件式使用" aria-label="Permalink to &quot;组件式使用&quot;"></a></h4>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Lottie </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'lottie-react'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> animationData </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> './animation.json'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Animation</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Lottie</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    animationData</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{animationData}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    loop</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    autoplay</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    style</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{{ width: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">400</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, height: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">400</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  /></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Animation;</span></span></code></pre>
</div><h4 id="hook-式使用" tabindex="-1">Hook 式使用 <a class="header-anchor" href="#hook-式使用" aria-label="Permalink to &quot;Hook 式使用&quot;"></a></h4>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { useLottie } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'lottie-react'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> animationData </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> './animation.json'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Animation</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">View</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">play</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">pause</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">stop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useLottie</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    animationData,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    loop: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    autoplay: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  });</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {View}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onClick</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> play</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()}>播放&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onClick</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> pause</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()}>暂停&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onClick</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> stop</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()}>停止&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  );</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Animation;</span></span></code></pre>
</div><h3 id="高级功能" tabindex="-1">高级功能 <a class="header-anchor" href="#高级功能" aria-label="Permalink to &quot;高级功能&quot;"></a></h3>
<h4 id="动画控制" tabindex="-1">动画控制 <a class="header-anchor" href="#动画控制" aria-label="Permalink to &quot;动画控制&quot;"></a></h4>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { useRef } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'react'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Lottie, { LottieRefCurrentProps } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'lottie-react'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> animationData </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> './animation.json'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ControlledAnimation</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> lottieRef</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useRef</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">LottieRefCurrentProps</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> handleComplete</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'动画播放完成'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Lottie</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        lottieRef</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{lottieRef}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        animationData</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{animationData}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        loop</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        onComplete</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{handleComplete}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      /></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onClick</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lottieRef.current?.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">setSpeed</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)}></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        2倍速播放</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onClick</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lottieRef.current?.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">goToAndPlay</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">30</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)}></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        跳转到第30帧并播放</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  );</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ControlledAnimation;</span></span></code></pre>
</div><h2 id="性能优化建议" tabindex="-1">性能优化建议 <a class="header-anchor" href="#性能优化建议" aria-label="Permalink to &quot;性能优化建议&quot;"></a></h2>
<ol>
<li>
<p><strong>选择合适的渲染器</strong></p>
<ul>
<li>SVG: 适用于小型、交互性强的动画</li>
<li>Canvas: 适用于大型、复杂的动画</li>
</ul>
</li>
<li>
<p><strong>优化动画资源</strong></p>
<ul>
<li>压缩 JSON 文件</li>
<li>移除未使用的关键帧</li>
<li>适当降低动画帧率</li>
</ul>
</li>
<li>
<p><strong>按需加载</strong></p>
<ul>
<li>使用动态导入延迟加载动画资源</li>
<li>考虑在适当时机销毁动画实例</li>
</ul>
</li>
<li>
<p><strong>缓存策略</strong></p>
<ul>
<li>对频繁使用的动画进行缓存</li>
<li>考虑使用 Service Worker 缓存动画资源</li>
</ul>
</li>
</ol>
<h2 id="最佳实践" tabindex="-1">最佳实践 <a class="header-anchor" href="#最佳实践" aria-label="Permalink to &quot;最佳实践&quot;"></a></h2>
<ol>
<li>
<p><strong>文件管理</strong></p>
<div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>src/</span></span>
<span class="line"><span>├── assets/</span></span>
<span class="line"><span>│   └── animations/     # 存放动画JSON文件</span></span>
<span class="line"><span>├── components/</span></span>
<span class="line"><span>│   └── animations/     # 存放动画组件</span></span></code></pre>
</div></li>
<li>
<p><strong>组件封装</strong></p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// src/components/animations/AnimationWrapper.tsx</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> Lottie, { LottieProps } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'lottie-react'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">interface</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Props</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> extends</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Partial</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">LottieProps</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> {</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  name</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  size</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> number</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> AnimationWrapper</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ({ </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">size</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 200</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">props</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Props</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Lottie</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    animationData</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">require</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`@/assets/animations/${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}.json`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    style</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{{ width: size, height: size }}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    {</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">props}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  /></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> AnimationWrapper;</span></span></code></pre>
</div></li>
<li>
<p><strong>错误处理</strong></p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Animation</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> handleError</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">err</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'Lottie 动画加载失败:'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, err);</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">    // 显示后备UI</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">ErrorBoundary</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> fallback</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">FallbackUI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> />}></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Lottie</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        animationData</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{animationData}</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">        onError</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{handleError}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      /></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">ErrorBoundary</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  );</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">};</span></span></code></pre>
</div></li>
</ol>
<p>Lottie 为 Web 开发带来了设计与开发的无缝衔接，大幅提升了动画开发效率。通过合理选择实现方案、遵循最佳实践，我们可以轻松地在 Web 应用中实现高质量的动画效果。</p>
]]></content:encoded>
            <author>Booling</author>
            <category>Development</category>
            <category>FrontEnd</category>
            <category>Study</category>
        </item>
        <item>
            <title><![CDATA[移动应用图标设计]]></title>
            <link>https://blog.booling.cn/posts/design/expo-app-icon-design</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/design/expo-app-icon-design</guid>
            <pubDate>Fri, 07 Feb 2025 16:20:09 GMT</pubDate>
            <description><![CDATA[设计和配置 Splash Screen 与 App Icon]]></description>
            <content:encoded><![CDATA[<h1 id="使用-expo-构建-android-和-ios-应用时设计和配置-splash-screen-与-app-icon" tabindex="-1"><strong>使用 Expo 构建 Android 和 iOS 应用时设计和配置 Splash Screen 与 App Icon</strong> <a class="header-anchor" href="#使用-expo-构建-android-和-ios-应用时设计和配置-splash-screen-与-app-icon" aria-label="Permalink to &quot;**使用 Expo 构建 Android 和 iOS 应用时设计和配置 Splash Screen 与 App Icon**&quot;"></a></h1>
<p>前段时间在推动「<strong>华师匣子</strong>」完全重构后的 3.0 版本的上线，应用上线当然离不开一个好看的门头，于是着手配置匣子的 <strong>启动画面（Splash Screen）</strong> 与 <strong>应用图标（App Icon）</strong>。起初以为只需准备一张图交给 Expo 配置即可，但真正深入研究时，才发现 iOS 和 Android 在图标与启动页方面的设计理念与规范差异不小。</p>
<hr>
<h2 id="一、平台差异与设计理念对比" tabindex="-1"><strong>一、平台差异与设计理念对比</strong> <a class="header-anchor" href="#一、平台差异与设计理念对比" aria-label="Permalink to &quot;**一、平台差异与设计理念对比**&quot;"></a></h2>
<p>在设计 Splash Screen 与 App Icon 之前，了解 Google 与 Apple 的设计哲学非常重要，它们的出发点不同，最终呈现方式也有所区别：</p>
<table tabindex="0">
<thead>
<tr>
<th><strong>项目</strong></th>
<th><strong>Apple（iOS）理念</strong></th>
<th><strong>Google（Android）理念</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>App Icon</td>
<td>强调极简和一致性。图标不带圆角，由系统统一加遮罩，保持系统风格。</td>
<td>强调灵活性。支持自定义前景与背景，系统应用多种遮罩（圆形、泪滴等）。</td>
</tr>
<tr>
<td>Splash Screen</td>
<td>作为功能性过渡界面，不允许动画或延时，仅允许展示静态图像与背景色。</td>
<td>支持动画启动（Android 12 起），但强调流畅体验，建议简洁过渡。</td>
</tr>
</tbody>
</table>
<p><img src="https://docs-assets.developer.apple.com/published/298204fa29c2dc771deb8651963ce75a/app-icons-platform-appearance-overview%402x.png" alt="Apple App Icon Example"></p>
<p><img src="https://image.baidu.com/search/down?url=https://tvax4.sinaimg.cn/large/007CWdRmly1i2rt4fxfesj31a20u81as.jpg" alt="Android App Icon Example"></p>
<hr>
<h2 id="二、app-icon-设计指南" tabindex="-1"><strong>二、App Icon 设计指南</strong> <a class="header-anchor" href="#二、app-icon-设计指南" aria-label="Permalink to &quot;**二、App Icon 设计指南**&quot;"></a></h2>
<h3 id="ios-平台" tabindex="-1"><strong>iOS 平台</strong> <a class="header-anchor" href="#ios-平台" aria-label="Permalink to &quot;**iOS 平台**&quot;"></a></h3>
<p>根据 <a href="https://developer.apple.com/design/human-interface-guidelines/app-icons/" target="_blank" rel="noreferrer">Apple 官方 HIG</a>：</p>
<ul>
<li>必须为正方形图标（1024×1024 px）。</li>
<li><strong>不要添加圆角</strong>，系统会自动处理。</li>
<li>图标应具有识别度，避免文字或复杂元素。</li>
<li>背景必须不透明（纯白或品牌色），不要留空或透明区域。</li>
</ul>
<p><img src="https://static.uxbaike.com/uploads/2024/01/d2b5ca33bd970f64a6301fa75ae2eb22-307.png" alt="iOS App Icon Design Template"></p>
<hr>
<h3 id="android-平台" tabindex="-1"><strong>Android 平台</strong> <a class="header-anchor" href="#android-平台" aria-label="Permalink to &quot;**Android 平台**&quot;"></a></h3>
<p>参考 <a href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive?hl=zh-cn" target="_blank" rel="noreferrer">Android Adaptive Icon 设计规范</a>：</p>
<ul>
<li>图标分为前景图和背景色或图。</li>
<li>系统应用不同遮罩形状显示图标（圆形、泪滴、方形等）。</li>
<li>前景图像应居中，避免边缘贴边（保持安全区）。</li>
</ul>
<p><img src="https://cdn.ipfsscan.io/weibo/large/007CWdRmly1i2rt4fxfesj31a20u81as.jpg" alt="Android Icon Design"></p>
<hr>
<h3 id="推荐工具" tabindex="-1"><strong>推荐工具：</strong> <a class="header-anchor" href="#推荐工具" aria-label="Permalink to &quot;**推荐工具：**&quot;"></a></h3>
<ul>
<li>📠 <a href="https://developer.android.com/studio/write/create-app-icons?hl=zh-cn" target="_blank" rel="noreferrer">官方图标制作工具</a>：轻松创建用于 Android 的图标</li>
<li>🎨 <a href="https://www.figma.com/community/file/1466490409418563617" target="_blank" rel="noreferrer">Figma 图标设计模板</a>：已适配 iOS 与 Android 尺寸</li>
<li>🛠️ <a href="https://docs.expo.dev/expo-cli/command-reference/optimize/" target="_blank" rel="noreferrer">expo-optimize</a>：用于图像压缩优化</li>
</ul>
<hr>
<h2 id="三、splash-screen-设计建议" tabindex="-1"><strong>三、Splash Screen 设计建议</strong> <a class="header-anchor" href="#三、splash-screen-设计建议" aria-label="Permalink to &quot;**三、Splash Screen 设计建议**&quot;"></a></h2>
<p>Splash Screen 是用户点击图标后的第一屏，其作用是承接启动过程、展现品牌感。</p>
<h3 id="通用设计建议" tabindex="-1"><strong>通用设计建议：</strong> <a class="header-anchor" href="#通用设计建议" aria-label="Permalink to &quot;**通用设计建议：**&quot;"></a></h3>
<ul>
<li>使用品牌图标或标志性元素，简洁为主。</li>
<li>图像居中显示，背景色与品牌主色调一致。</li>
<li>避免文字或加载动画，保持统一视觉体验。</li>
</ul>
<h3 id="ios-注意事项" tabindex="-1"><strong>iOS 注意事项</strong> <a class="header-anchor" href="#ios-注意事项" aria-label="Permalink to &quot;**iOS 注意事项**&quot;"></a></h3>
<p>Apple 不允许控制 Splash 停留时间，不允许加入动画。开发者需通过配置 LaunchScreen.storyboard 实现固定启动页。</p>
<p><img src="https://www.applin.dev/docs/ios/launch_screen.storyboard.png" alt=""></p>
<h3 id="android-支持动画启动-12-但建议尽量保持风格一致-不使用复杂动效。" tabindex="-1"><strong>Android 支持动画启动（12+），但建议尽量保持风格一致，不使用复杂动效。</strong> <a class="header-anchor" href="#android-支持动画启动-12-但建议尽量保持风格一致-不使用复杂动效。" aria-label="Permalink to &quot;**Android 支持动画启动（12+），但建议尽量保持风格一致，不使用复杂动效。**&quot;"></a></h3>
<p><video alt="显示 Google Gmail 应用启动画面的视频" controls="" src="https://developer.android.com/static/images/guide/topics/ui/splash-screen/splash-screen-gmail-example.mp4?hl=zh-cn" width="40%"></video></p>
<hr>
<h2 id="四、expo-中的配置方式" tabindex="-1"><strong>四、Expo 中的配置方式</strong> <a class="header-anchor" href="#四、expo-中的配置方式" aria-label="Permalink to &quot;**四、Expo 中的配置方式**&quot;"></a></h2>
<p>Expo 提供了集中化的配置方式，统一管理图标和启动页资源。只需编辑 app.json 或 app.config.js。</p>
<h3 id="app-icon-配置示例" tabindex="-1"><strong>App Icon 配置示例</strong> <a class="header-anchor" href="#app-icon-配置示例" aria-label="Permalink to &quot;**App Icon 配置示例**&quot;"></a></h3>
<div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>{</span></span>
<span class="line"><span>  "expo": {</span></span>
<span class="line"><span>    "icon": "./assets/icon.png"</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
</div><blockquote>
<p>推荐尺寸：1024×1024 px<br>
不添加圆角、不透明背景</p>
</blockquote>
<p>对于 Android 自定义 Adaptive Icon：</p>
<div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>{</span></span>
<span class="line"><span>  "expo": {</span></span>
<span class="line"><span>    "android": {</span></span>
<span class="line"><span>      "adaptiveIcon": {</span></span>
<span class="line"><span>        "foregroundImage": "./assets/adaptive-icon.png",</span></span>
<span class="line"><span>        "backgroundColor": "#FFFFFF"</span></span>
<span class="line"><span>      }</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
</div><hr>
<h3 id="splash-screen-配置示例" tabindex="-1"><strong>Splash Screen 配置示例</strong> <a class="header-anchor" href="#splash-screen-配置示例" aria-label="Permalink to &quot;**Splash Screen 配置示例**&quot;"></a></h3>
<div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>{</span></span>
<span class="line"><span>  "expo": {</span></span>
<span class="line"><span>    "splash": {</span></span>
<span class="line"><span>      "image": "./assets/splash.png",</span></span>
<span class="line"><span>      "resizeMode": "contain",</span></span>
<span class="line"><span>      "backgroundColor": "#ffffff"</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
</div><p>字段说明：</p>
<ul>
<li>image: 建议透明 PNG 格式的 logo。</li>
<li>resizeMode:
<ul>
<li>contain: 居中缩放，不裁剪。</li>
<li>cover: 全屏填充，可能裁剪边缘。</li>
</ul>
</li>
<li>backgroundColor: 建议与主 UI 保持一致。</li>
</ul>
<hr>
<h2 id="五、调试建议" tabindex="-1"><strong>五、调试建议</strong> <a class="header-anchor" href="#五、调试建议" aria-label="Permalink to &quot;**五、调试建议**&quot;"></a></h2>
<ul>
<li>使用 npx expo start 快速预览配置效果。</li>
<li>使用 expo run:android / expo run:ios 构建真机测试。</li>
<li>若 iOS 上启动页不显示，可参考 Apple 的 <a href="https://developer.apple.com/documentation/technotes/tn3118-debugging-your-apps-launch-screen" target="_blank" rel="noreferrer">TN3118 技术文档</a>。</li>
</ul>
<hr>
<h2 id="六、尺寸汇总表" tabindex="-1"><strong>六、尺寸汇总表</strong> <a class="header-anchor" href="#六、尺寸汇总表" aria-label="Permalink to &quot;**六、尺寸汇总表**&quot;"></a></h2>
<table tabindex="0">
<thead>
<tr>
<th><strong>项目</strong></th>
<th><strong>推荐尺寸</strong></th>
<th><strong>文件格式</strong></th>
<th><strong>注意事项</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>App Icon</td>
<td>1024×1024 px</td>
<td>PNG</td>
<td>不透明背景，无圆角</td>
</tr>
<tr>
<td>Adaptive Icon 前景</td>
<td>432×432 px</td>
<td>PNG</td>
<td>居中留边距，避免贴边裁剪</td>
</tr>
<tr>
<td>Splash Image</td>
<td>1242×2436 px</td>
<td>PNG</td>
<td>中心图像，背景色匹配 UI</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="七、总结" tabindex="-1"><strong>七、总结</strong> <a class="header-anchor" href="#七、总结" aria-label="Permalink to &quot;**七、总结**&quot;"></a></h2>
<p>设计图标和启动页看似细节，但却直接影响用户的第一印象。如果你正在使用 Expo 开发 App，建议在早期就将这些资源准备好，确保在 Android 与 iOS 上都有一致且合规的体验。</p>
<blockquote>
<p>参考链接</p>
<ul>
<li><a href="https://developer.apple.com/design/human-interface-guidelines/app-icons/" target="_blank" rel="noreferrer">苹果官方指南</a></li>
<li><a href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive?hl=zh-cn" target="_blank" rel="noreferrer">安卓官方指南</a></li>
<li><a href="https://www.uxbaike.com/post/401/2385" target="_blank" rel="noreferrer">UI 百科</a></li>
<li><a href="https://sspai.com/post/40223" target="_blank" rel="noreferrer">少数派</a></li>
<li><a href="https://pixso.cn/designskills/tubiaochicunguifan/" target="_blank" rel="noreferrer">尺寸参考</a></li>
</ul>
</blockquote>
]]></content:encoded>
            <author>Booling</author>
            <category>Design</category>
            <category>FrontEnd</category>
            <category>Mobile</category>
            <enclosure url="https://docs-assets.developer.apple.com/published/298204fa29c2dc771deb8651963ce75a/app-icons-platform-appearance-overview%402x.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[React 组件封装]]></title>
            <link>https://blog.booling.cn/posts/development/component-encapsulation</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/component-encapsulation</guid>
            <pubDate>Fri, 07 Feb 2025 16:20:09 GMT</pubDate>
            <description><![CDATA[简要介绍一下 React 中的组件封装]]></description>
            <content:encoded><![CDATA[<h1 id="react-项目中的组件封装" tabindex="-1">React 项目中的组件封装 <a class="header-anchor" href="#react-项目中的组件封装" aria-label="Permalink to &quot;React 项目中的组件封装&quot;"></a></h1>
<p>在前端开发中，组件封装指的是将 UI 的一部分（例如一个按钮、一个表单、一个对话框等）及其相关的逻辑（例如数据获取、事件处理、状态管理等）组合成一个独立的、可复用的单元，这个单元就称为组件。</p>
<p>组件封装的目标是提高代码的可复用性、可维护性和可测试性。通过将 UI 和逻辑封装到组件中，我们可以：</p>
<ul>
<li><strong>提高代码复用性:</strong> 将常用的 UI 元素封装成组件，可以在不同的页面或项目中重复使用，避免重复编写相同的代码。</li>
<li><strong>提高代码可维护性:</strong> 当需要修改 UI 或逻辑时，只需要修改组件的代码，而不需要修改多个地方的代码。</li>
<li><strong>提高代码可测试性:</strong> 组件可以独立进行测试，更容易发现和修复 bug。</li>
<li><strong>提升开发效率:</strong> 使用组件可以加快开发速度，因为开发者可以直接使用现成的组件，而不需要从头开始编写代码。</li>
<li><strong>增强代码组织结构:</strong> 将代码拆分成更小、更易于管理的单元，使代码更具可读性和可理解性。</li>
</ul>
<p>组件封装的核心思想:</p>
<ul>
<li><strong>封装性:</strong> 组件内部的实现细节对外隐藏，只暴露必要的接口供外部使用。</li>
<li><strong>可复用性:</strong> 组件可以在不同的场景下重复使用。</li>
<li><strong>可组合性:</strong> 组件可以像积木一样组合成更复杂的 UI。</li>
</ul>
<h2 id="基础的组件封装" tabindex="-1">基础的组件封装 <a class="header-anchor" href="#基础的组件封装" aria-label="Permalink to &quot;基础的组件封装&quot;"></a></h2>
<p>以 React 中最常用的 <em>函数式组件</em> 为例，一个最简单的组件封装如下：</p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Greet</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">name</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Hello {name} &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// using the functional component</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Greet</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Alice"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Greet</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"Bob"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">/></span></span></code></pre>
</div><p>通过更改传入组件的 <code>props</code> 值，我们就可以复用这个封装好的组件。当然，有时组件不仅仅用于数据渲染，也用于与用户交互：</p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">interface</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ButtonProps</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  onClick</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> void</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  disabled</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> boolean</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  children</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ReactNode</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Button</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">ButtonProps</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">> </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ({ </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">onClick</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">disabled</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">children</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onClick</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{onClick} </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">disabled</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{disabled}></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {children}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  )</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><p>组件也可以拥有自己的 <code>state</code>，以实现响应式的数据渲染与操作：</p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Counter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">count</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">setCount</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useState</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>你点击了按钮 {count} 次&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onClick</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setCount</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">prevCount</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> prevCount </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)}></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        点击我</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  )</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><p>看到这里，恭喜你已经入门了 React 的组件封装。但实际开发中，组件承担的功能往往更加复杂，想要真正优雅地封装出好的组件水是很深的。</p>
<h2 id="更进一步" tabindex="-1">更进一步 <a class="header-anchor" href="#更进一步" aria-label="Permalink to &quot;更进一步&quot;"></a></h2>
<p>在实际的业务中，前端的数据大部分要从后端获取，而这个过程中常常涉及异步操作。在一个组件中进行异步操作，一般需要依靠 <code>useEffect</code> 钩子：</p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> AsyncComponentPromise</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">setData</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useState</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">setError</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useState</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">loading</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">setLoading</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useState</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  useEffect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    fetch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/api/data"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">then</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">response</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">response.ok) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">          throw</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`HTTP error! status: ${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">response</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">status</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">}`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">        return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> response.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">json</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      })</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">then</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">jsonData</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setData</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(jsonData))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">catch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setError</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(error))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      .</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">finally</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(() </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> setLoading</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">))</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }, [])</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (loading) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Loading...&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (error) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Error：{error.message}&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (data) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">h1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Loaded&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">h1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">ul</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">          {data.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">map</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">((</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">item</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">            &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">li</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> key</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{item.id}>{item.name}&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">li</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">          ))}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">ul</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    )</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>No Data&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><p>我们为什么需要 <code>useEffect</code> 钩子来进行异步操作呢？答案很简单，React 作为一个 UI 框架，其核心在于渲染出页面，所以整个函数式组件的函数体，实际上是构建、渲染这个组件的流程，一旦我们在渲染流程中直接进行异步操作，就会导致这个流程需要等待异步操作结束，即阻塞了组件的渲染。</p>
<p>而 <code>useEffect</code> 钩子为了解决这个问题，其接收的函数将会在组件渲染完成后执行，这样就避免了异步操作对组件渲染的阻塞。</p>
<p>实际上应该放在 <code>useEffect</code> 钩子中执行的不仅仅是异步操作，还有例如修改特殊变量、进行 I/O 操作、抛出异常等代码，这些有一个统称叫做 <em>副作用</em>，这里不展开讲解。</p>
<p>另一个非常有用的钩子叫做 <code>useContext</code>，它是 React 中 <em>状态提升</em> 操作的高级解决方案，可以将状态跨越多层组件透传，一个典型的例子是用它实现侧边栏这种布局状态的控制：</p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { createContext, useContext, useState } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "react"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> SidebarContext</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> createContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> SidebarProvider</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> ({ </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">children</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 在 Provider 中保存状态，并提供给内部组件访问</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">isSidebarOpen</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">setIsSidebarOpen</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useState</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> toggleSidebar</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    setIsSidebarOpen</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">isSidebarOpen)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">SidebarContext.Provider</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{{ isSidebarOpen, toggleSidebar }}></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {children}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">SidebarContext.Provider</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  )</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useSidebar</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> context</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useContext</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(SidebarContext)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (context </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> undefined</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">    throw</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"useSidebar must be used within a SidebarProvider"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> context</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Sidebar</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">  // 获取状态</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">isSidebarOpen</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">toggleSidebar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useSidebar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">aside</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">      className</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">`bg-gray-800 text-white w-64 ${</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        isSidebarOpen</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "block"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> :</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "hidden"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">      }`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* Sidebar content */</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onClick</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{toggleSidebar}>关闭侧边栏&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">aside</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  )</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { SidebarProvider, Sidebar, useSidebar }</span></span></code></pre>
</div><p>在使用组件时，要先创建 Sidebar 上下文，再调用 Sidebar 组件：</p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> App</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">toggleSidebar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> useSidebar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">SidebarProvider</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> className</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"flex"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Sidebar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">main</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> className</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"p-4"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">          {</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* Main content */</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">          &#x3C;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> onClick</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{toggleSidebar}>打开侧边栏&#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">main</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">SidebarProvider</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  )</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><h2 id="组件样式" tabindex="-1">组件样式 <a class="header-anchor" href="#组件样式" aria-label="Permalink to &quot;组件样式&quot;"></a></h2>
<p>前端开发另一个重要的部分就是样式的实现了，虽然用来编写样式的 Extension 有很多，但要利用好他们其实也需要一些技巧。例如刚接触前端开发的人最常犯的错误就是 css 选择器运用不熟练，导致出现非常多不同类名但拥有重复的代码。</p>
<p>下面我们来看看几大组件库的样式实现方式。</p>
<h3 id="ant-design" tabindex="-1">Ant Design <a class="header-anchor" href="#ant-design" aria-label="Permalink to &quot;Ant Design&quot;"></a></h3>
<p>AntD 采用的是 Design Token 模式，通过预定义的原子化 Token 来自定义样式：</p>
<div class="language-tsx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { Button, ConfigProvider, Space } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "antd"</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> React </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "react"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> App</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> React</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">FC</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">ConfigProvider</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    theme</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{{</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      token: {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // Seed Token，影响范围大</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        colorPrimary: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"#00b96b"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        borderRadius: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        // 派生变量，影响范围小</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        colorBgContainer: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"#f6ffed"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      },</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }}</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Space</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> type</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"primary"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Primary&#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Default&#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">Space</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">ConfigProvider</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> App</span></span></code></pre>
</div><p>这种模式许多优点：</p>
<ul>
<li><strong>一致性:</strong> 设计令牌确保在整个应用程序中使用相同的颜色、字体、间距等样式。 更改一个令牌的值会自动更新所有使用该令牌的地方，从而减少了维护工作量并避免了不一致性。</li>
<li><strong>可维护性:</strong> 集中管理设计令牌使得修改样式变得更容易。 开发者只需要修改令牌的值，而不需要在整个代码库中搜索和替换样式。</li>
<li><strong>可扩展性:</strong> 设计令牌使得设计系统更容易扩展。 可以轻松添加新的令牌，而不会影响现有代码。</li>
<li><strong>可重用性:</strong> 设计令牌可以跨多个项目和平台重用，从而提高了效率。</li>
<li><strong>协作性:</strong> 设计令牌为设计师和开发者提供了一种通用的语言，方便他们协作。 设计师可以定义令牌，开发者可以使用这些令牌来构建 UI 组件。</li>
<li><strong>主题定制:</strong> 通过修改设计令牌，可以轻松地创建不同的主题，例如深色模式或品牌主题。</li>
</ul>
<p>但同时也有许多缺陷：</p>
<ul>
<li><strong>学习成本:</strong> 需要学习如何使用设计令牌系统，这对于团队成员来说可能需要一些时间和精力。</li>
<li><strong>初始设置成本:</strong> 设置设计令牌系统需要一些前期工作，例如定义令牌、创建工具等。</li>
<li><strong>复杂性:</strong> 对于小型项目，设计令牌系统可能显得过于复杂，得不偿失。 管理大量的令牌也可能变得复杂。</li>
<li><strong>工具依赖:</strong> 通常需要使用一些工具来管理和使用设计令牌，例如 Style Dictionary 或类似的工具。 这增加了项目的依赖性。</li>
<li><strong>潜在的命名冲突:</strong> 如果令牌命名不当，可能会导致命名冲突。 需要制定一个清晰的命名约定来避免这种情况。</li>
<li><strong>调试难度:</strong> 追踪样式问题可能变得更困难，因为需要追踪令牌的层层映射关系。</li>
</ul>
<h3 id="shadcn-ui" tabindex="-1">shadcn/ui <a class="header-anchor" href="#shadcn-ui" aria-label="Permalink to &quot;shadcn/ui&quot;"></a></h3>
<p>shadcn/ui 与 AntD 差别十分明显，其采用 Headless UI 理念构建：</p>
<p>Headless UI 是一种新型的 UI 组件开发模式，它只关心行为逻辑，不涉及 UI 的具体实现，从而允许开发者自由定制 UI，这种设计思想符合开闭原则。</p>
<p>目前比较出众的是 Radix、headlessui，主要都是解决 Behavior Libraries 层面的问题。旨在提供一套开放、无控制、无样式的基础组件，方便开发者进行进一步的个性化封装。我的探索之旅中，我读过、试过这两者，最终决定更深入地使用 Radix，主要是因为 <a href="https://github.com/shadcn-ui/ui" target="_blank" rel="noreferrer">shadcn/ui</a> 这个优秀项目也是建立在 Radix 的基础上！以下是 Radix 的几大核心理念：</p>
<ul>
<li>可访问性（Accessible）：如果你需要考虑应用的可访问性（残疾人士友好），Radix 的设计遵循 <strong><code>WAI-ARIA</code></strong> 规范，这是 W3C 编写的规范，定义了一组可用于其他元素的 HTML 特性，用于提供额外的语义化以及改善无障碍体验。</li>
<li>无样式（Unstyled）：正如其名，Radix 提供的组件不包含任何预设风格，完全自由地配合任何样式方案，这也直击了自定义样式的痛点。</li>
<li>开放性（Opened）：Radix 的开放性极佳，每一个组件都是独立的单元，可自由组合、灵活配置，满足你的各种需求。</li>
</ul>
<p>shadcn/ui 是 Vercel 的工程师推出的一款组件合集，建立在 Tailwind CSS 和 Radix UI 之上，目前包括了48个独立组件。根据<a href="https://ui.shadcn.com/docs" target="_blank" rel="noreferrer">官方说明</a>，这款产品被定义为「组件合集」而非传统的「组件库」，其独到之处在于：不通过 npm 安装，而是直接将组件源代码复制粘贴到项目中，这样极大地方便了用户根据自己的需求去修改和扩展代码。</p>
<p>与传统组件库相比，shadcn/ui 遵循以下设计原则：</p>
<ul>
<li><strong>避免不必要的依赖</strong>：不把整个库作为依赖项添加，有助于减少项目体积，从而提升应用的加载速度。</li>
<li><strong>组件代码的直接编辑</strong>：由于使用复制和粘贴的方式加入项目，提供了直接访问每个组件源代码的能力，开发者可以直接访问和控制每个组件的行为、样式和 DOM 结构，这种灵活性让 shadcn/ui 在众多 UI 解决方案中脱颖而出。</li>
<li><strong>细粒度的控制</strong>：每一个组件都是独立的单元，可以单独使用和定制，这种模块化的设计不仅简化了个别组件的定制过程，也便于整体 UI 系统的扩展和维护。</li>
<li><strong>多层次的样式自定义</strong>：首先，shadcn/ui 提供了图形界面的主题编辑器，允许开发者在不直接修改 CSS 的情况下，通过编辑器定制一系列样式（如颜色、字体、边距等）；其次，开发者还可以在组件源代码层面进行个性化调整，或在使用组件时直接在标签上添加 className；最后，通过 RenderProps 方式进一步扩展 UI，开发者可以拿到当前上下文的状态，天然适合对 UI 的自定义扩展。这种多维度的自定义能力极大增强了 UI 的灵活性和适应性。</li>
</ul>
<p>例如，使用 shadcn/ui 中的 Tabs 组件时，可以通过以下命令简单地添加到项目中：</p>
<div class="language-html vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">Tabs</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> defaultValue</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"account"</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> className</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"w-full"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">TabsList</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">TabsTrigger</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"account"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Account&#x3C;/</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">TabsTrigger</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">TabsTrigger</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"password"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>Password&#x3C;/</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">TabsTrigger</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;/</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">TabsList</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">TabsContent</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"account"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>在这里修改你的账号设置&#x3C;/</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">TabsContent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  &#x3C;</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">TabsContent</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"password"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">>在这里修改你的密码&#x3C;/</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">TabsContent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;/</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">Tabs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span></code></pre>
</div><p>执行 <strong><code>npx shadcn-ui@latest add tabs</code></strong> 后，Tabs 组件会被安装到 <strong><code>./components/ui/tabs.tsx</code></strong>，此时开发者可以直接编辑源代码以定制 Tabs 组件。Tabs 组件自身被设计为多个可单独定制的子组件（如 Tabs, TabsContent, TabsList, TabsTrigger），这为定制和风格化提供了极大的便利。</p>
<p>shadcn/ui 的设计理念是将交互逻辑的复杂性留给组件维护者，而将 UI 的定制性最大化地交给使用者，实现了业务需求的高度定制化。这种做法符合软件设计原则「关注点分离」（Separation of concerns，SoC），不过也带来了一些挑战：</p>
<ul>
<li><strong>对开发者的要求较高</strong>：需要良好的抽象设计能力来处理无UI层的组件。</li>
<li><strong>较高的使用成本&amp;升级成本</strong>：完全自定义的 UI 层可能带来更大的开发成本，未来的更新升级也比较麻烦，需要仔细评估成本和收益。</li>
</ul>
]]></content:encoded>
            <author>Booling</author>
            <category>Development</category>
            <category>FrontEnd</category>
            <category>Study</category>
        </item>
        <item>
            <title><![CDATA[前端工程化初见]]></title>
            <link>https://blog.booling.cn/posts/development/frontend-engineering</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/frontend-engineering</guid>
            <pubDate>Fri, 29 Nov 2024 18:16:35 GMT</pubDate>
            <description><![CDATA[介绍一下前端工程化当中的各种技术]]></description>
            <content:encoded><![CDATA[<h1 id="前端工程化" tabindex="-1">前端工程化 <a class="header-anchor" href="#前端工程化" aria-label="Permalink to &quot;前端工程化&quot;"></a></h1>
<p>作为一个练习时长两年半的前端练习生，个人认为前端目前资料最零散的部分，就是前端项目的工程化。在目前前端基础技术相对固定且成熟的情况下，项目工程化所需的各种知识就更加能够体现出一名前端程序员的项目经验。</p>
<h2 id="工程规范" tabindex="-1">工程规范 <a class="header-anchor" href="#工程规范" aria-label="Permalink to &quot;工程规范&quot;"></a></h2>
<h3 id="从-git-讲起" tabindex="-1">从 Git 讲起 <a class="header-anchor" href="#从-git-讲起" aria-label="Permalink to &quot;从 Git 讲起&quot;"></a></h3>
<p>版本控制系统在软件工程领域中无疑是十分重要的一个部分，而 Git 则是其中最优秀的一个解决方案。不管是在大型项目还是小玩具中，Git 的使用都可以提高开发与协作的效率。</p>
<h4 id="版本控制" tabindex="-1">版本控制 <a class="header-anchor" href="#版本控制" aria-label="Permalink to &quot;版本控制&quot;"></a></h4>
<p>版本控制是 Git 的本职工作，其在大型项目中无论是性能还是效果上都表现极佳。通过追踪分支或文件的 commit 历史，可以很方便地回滚版本、处理 bug。</p>
<p>为了方便版本控制，大多数专业团队会要求开发者遵循提交规范，这里以 <a href="https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit" target="_blank" rel="noreferrer">Angular Commit</a> （<a href="https://zj-git-guide.readthedocs.io/zh-cn/latest/message/Angular%E6%8F%90%E4%BA%A4%E4%BF%A1%E6%81%AF%E8%A7%84%E8%8C%83/" target="_blank" rel="noreferrer">中文版</a>）规范为例。以这种规范的格式来撰写 Commit Message，可以大大提高版本控制过程中定位更改的效率，也可以让你的小伙伴一眼看懂你的 Commit 到底干了些啥。</p>
<h4 id="协作开发" tabindex="-1">协作开发 <a class="header-anchor" href="#协作开发" aria-label="Permalink to &quot;协作开发&quot;"></a></h4>
<p>Git 独特的分支系统设计给多人协作项目提供了非常好的开发管理方式，每个开发者可以在不同的 branch 上进行开发，各自的工作内容不会互相影响，在完成各个模块的开发后也可以非常方便地 merge 到主分支中。</p>
<p>在一个工程化的项目中，开发一个新功能的简要过程如下：</p>
<p><img src="https://cdn.ipfsscan.io/weibo/large/007CWdRmgy1hw3cwo9q6yj31o60k3gof.jpg" alt="image"></p>
<p>通过新建分支的形式，将开发新 Feature 的起点固定在某一个版本，可以在功能开发周期内保证基础代码的不变，避免开发过程中经常更新上游代码导致的一些临时 bug 和开发思路中断。</p>
<p>而 Pull Request 则可以为后续 Code Review 提供平台，保证代码质量。</p>
<h4 id="持续集成和部署" tabindex="-1">持续集成和部署 <a class="header-anchor" href="#持续集成和部署" aria-label="Permalink to &quot;持续集成和部署&quot;"></a></h4>
<p>一个成熟的前端工程化项目一定具备持续集成和部署的能力，通过版本控制系统与 CI/CD 系统之间的协作，可以自动化完成一系列重复的工作，提高开发效率。</p>
<h5 id="持续集成-ci" tabindex="-1">持续集成（CI） <a class="header-anchor" href="#持续集成-ci" aria-label="Permalink to &quot;持续集成（CI）&quot;"></a></h5>
<p>持续集成（CI）是指将多个开发者的代码变更频繁地集成到一个共享的代码仓库中。这个过程通常涉及以下几个步骤：</p>
<ul>
<li><strong>代码提交</strong>：开发者将代码更改提交到版本控制系统（如Git）。</li>
<li><strong>自动构建</strong>：每次提交后，系统会自动构建代码并运行测试，以确保新代码没有引入错误。</li>
<li><strong>早期发现问题</strong>：通过频繁的集成，团队能够及早发现和解决代码冲突和缺陷，从而避免在后期阶段出现更大的问题。</li>
</ul>
<h5 id="持续交付与持续部署-cd" tabindex="-1">持续交付与持续部署（CD） <a class="header-anchor" href="#持续交付与持续部署-cd" aria-label="Permalink to &quot;持续交付与持续部署（CD）&quot;"></a></h5>
<p>持续交付和持续部署（CD）是CI的延伸，二者有细微差别：</p>
<ul>
<li><strong>持续交付</strong>：指的是在完成自动化测试后，代码可以随时部署到生产环境，但实际的部署过程可能仍需要人工干预。这种方式确保了软件始终处于可发布状态，能够快速响应市场需求和用户反馈。</li>
<li><strong>持续部署</strong>：则是将所有通过测试的代码自动部署到生产环境中，不需要人工干预。这意味着每次代码变更都能迅速推送给最终用户，从而加快了反馈循环和迭代速度。</li>
</ul>
<h5 id="ci-cd的优势" tabindex="-1">CI/CD的优势 <a class="header-anchor" href="#ci-cd的优势" aria-label="Permalink to &quot;CI/CD的优势&quot;"></a></h5>
<ul>
<li><strong>提高效率</strong>：通过自动化构建、测试和部署，减少了手动操作，提高了开发团队的工作效率。</li>
<li><strong>降低风险</strong>：小规模、频繁的更新使得问题更易于识别和修复，从而降低了大规模发布带来的风险。</li>
<li><strong>快速反馈</strong>：频繁的发布周期让开发团队能够快速获得用户反馈，从而及时调整和优化产品。</li>
</ul>
<h5 id="工具与实践" tabindex="-1">工具与实践 <a class="header-anchor" href="#工具与实践" aria-label="Permalink to &quot;工具与实践&quot;"></a></h5>
<p>在CI/CD的实施过程中，有许多工具可以帮助团队实现自动化流程，例如：</p>
<ul>
<li><strong>持续集成工具</strong>：Jenkins、Travis CI、CircleCI等。</li>
<li><strong>持续交付/部署工具</strong>：Ansible、Puppet、AWS CodeDeploy等。</li>
<li><strong>监控工具</strong>：Nagios、Zabbix等，用于实时监控应用性能和健康状态。</li>
</ul>
<p>此外，最常用也最简单的工具其实是 GitHub Actions，这里不过多赘述，以后也许会另写文章。</p>
<h3 id="赏心悦目的代码风格" tabindex="-1">赏心悦目的代码风格 <a class="header-anchor" href="#赏心悦目的代码风格" aria-label="Permalink to &quot;赏心悦目的代码风格&quot;"></a></h3>
<p>前端语言的代码风格相较于其他语言比较灵活，每个人都可能有自己的习惯，例如缩进的 Tab 与空格、是否添加尾逗号、单双引号的使用、行末分号的添加……这些不同的代码风格如果不加以统一，会导致代码看起来十分混乱，也不方便后续的维护。这里介绍两个前端最常用的格式化工具：ESLint &amp; Prettier。</p>
<h4 id="eslint" tabindex="-1">ESLint <a class="header-anchor" href="#eslint" aria-label="Permalink to &quot;ESLint&quot;"></a></h4>
<p>ESLint 是一个 JS/TS 代码质量检查工具，侧重于检查语法错误以及代码中可能出现的错误。例如 <em>未被导出的成员</em>、 <em>未被使用的导入</em>……</p>
<h4 id="prettier" tabindex="-1">Prettier <a class="header-anchor" href="#prettier" aria-label="Permalink to &quot;Prettier&quot;"></a></h4>
<p>Prettier 是一个代码格式化工具，提供了对于多种语言代码的格式化支持，如 <code>.css</code>、<code>.sass</code>、<code>.scss</code>、<code>.jsx</code>、<code>.tsx</code>、<code>.vue</code> 等语言的文件。</p>
<p>两个工具侧重点不同，因此在前端项目中常常同时配置这两个工具来更加全面地规范代码风格。</p>
<h2 id="构建工具" tabindex="-1">构建工具 <a class="header-anchor" href="#构建工具" aria-label="Permalink to &quot;构建工具&quot;"></a></h2>
<p>前端项目的基础是 HTML、CSS 与 JavaScript，但仅通过这些语言来开发不够高效，也因此我们需要学习如 Vue、React 这样的前端框架，但这些框架都有着自己特殊的代码形式，而构建工具就是将基于这些框架的代码打包编译成 HTML、CSS 和 JavaScript 的程序。</p>
<h3 id="梦开始的-webpack" tabindex="-1">梦开始的 Webpack <a class="header-anchor" href="#梦开始的-webpack" aria-label="Permalink to &quot;梦开始的 Webpack&quot;"></a></h3>
<p>Webpack 是目前运用最广泛的前端项目打包工具，其主要工作是解析 js 代码间的依赖关系、将所有依赖的文件打包到最终的产物 js 文件中、将一些不统一的 ES 语法转换统一。</p>
<p>看起来很抽象，那么 Webpack 到底做了些什么事呢？</p>
<p>在一个完整的项目 当中，一般会有各种不同类型的资源文件，比如 <code>.js</code>、<code>.jsx</code>、<code>.css</code>、<code>.sass</code> 等文件，而 Webpack 所做的就是将这些资源文件进行依赖分析，并整合成一个整体，再分块输出成许多 <code>chunk</code> 便于加载。</p>
<p>这个过程核心完成了 <strong>内容转换 + 资源合并</strong> 两种功能，实现上包含三个阶段：</p>
<ol>
<li>初始化阶段：
<ol>
<li><strong>初始化参数</strong>：从配置文件、 配置对象、Shell 参数中读取，与默认配置结合得出最终的参数</li>
<li><strong>创建编译器对象</strong>：用上一步得到的参数创建 <code>Compiler</code> 对象</li>
<li><strong>初始化编译环境</strong>：包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等</li>
<li><strong>开始编译</strong>：执行 <code>compiler</code> 对象的 <code>run</code> 方法</li>
<li><strong>确定入口</strong>：根据配置中的 <code>entry</code> 找出所有的入口文件，调用 <code>compilition.addEntry</code> 将入口文件转换为 <code>dependence</code> 对象</li>
</ol>
</li>
<li>构建阶段：
<ol>
<li><strong>编译模块(make)</strong>：根据 <code>entry</code> 对应的 <code>dependence</code> 创建 <code>module</code> 对象，调用 <code>loader</code> 将模块转译为标准 JS 内容，调用 JS 解释器将内容转换为 AST 对象，从中找出该模块依赖的模块，再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理</li>
<li><strong>完成模块编译</strong>：上一步递归处理所有能触达到的模块后，得到了每个模块被翻译后的内容以及它们之间的 <strong>依赖关系图</strong></li>
</ol>
</li>
<li>生成阶段：
<ol>
<li><strong>输出资源(seal)</strong>：根据入口和模块之间的依赖关系，组装成一个个包含多个模块的 <code>Chunk</code>，再把每个 <code>Chunk</code> 转换成一个单独的文件加入到输出列表，这步是可以修改输出内容的最后机会</li>
<li><strong>写入文件系统(emitAssets)</strong>：在确定好输出内容后，根据配置确定输出的路径和文件名，把文件内容写入到文件系统</li>
</ol>
</li>
</ol>
<p>单次构建过程自上而下按顺序执行，下面会展开聊聊细节，在此之前，对上述提及的各类技术名词不太熟悉的同学，可以先看看简介：</p>
<ul>
<li><code>Entry</code>：编译入口，webpack 编译的起点</li>
<li><code>Compiler</code>：编译管理器，webpack 启动后会创建 <code>compiler</code> 对象，该对象一直存活知道结束退出</li>
<li><code>Compilation</code>：单次编辑过程的管理器，比如 <code>watch = true</code> 时，运行过程中只有一个 <code>compiler</code> 但每次文件变更触发重新编译时，都会创建一个新的 <code>compilation</code> 对象</li>
<li><code>Dependence</code>：依赖对象，webpack 基于该类型记录模块间依赖关系</li>
<li><code>Module</code>：webpack 内部所有资源都会以“module”对象形式存在，所有关于资源的操作、转译、合并都是以 “module” 为基本单位进行的</li>
<li><code>Chunk</code>：编译完成准备输出时，webpack 会将 <code>module</code> 按特定的规则组织成一个一个的 <code>chunk</code>，这些 <code>chunk</code> 某种程度上跟最终输出一一对应</li>
<li><code>Loader</code>：资源内容转换器，其实就是实现从内容 A 转换 B 的转换器</li>
<li><code>Plugin</code>：webpack构建过程中，会在特定的时机广播对应的事件，插件监听这些事件，在特定时间点介入编译过程</li>
</ul>
<h4 id="下一代的构建工具" tabindex="-1">下一代的构建工具！ <a class="header-anchor" href="#下一代的构建工具" aria-label="Permalink to &quot;下一代的构建工具！&quot;"></a></h4>
<p>Vite 被誉为下一代构建工具，以打包速度快、热更新速度快深受开发者喜爱。Vite 所做的事和 Webpack 相同，但在构建过程中的优化比 Webpack 更优秀，HMR 速度和 build 速度都比 Webpack 快很多。</p>
<p>另一方面，与 Webpack 相比，Vite 本身提供的功能十分简单，仅仅只是一个打包工具，因此其基础配置比 Webpack 简洁很多，而其它功能则通过各种官方/社区的插件作为补充。</p>
]]></content:encoded>
            <author>Booling</author>
            <category>Development</category>
            <category>FrontEnd</category>
            <category>Study</category>
            <enclosure url="https://cdn.ipfsscan.io/weibo/large/007CWdRmgy1hw3cwo9q6yj31o60k3gof.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[区块链技术初见]]></title>
            <link>https://blog.booling.cn/posts/development/block-chain</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/block-chain</guid>
            <pubDate>Wed, 13 Nov 2024 12:32:41 GMT</pubDate>
            <description><![CDATA[简单了解了一下区块链技术]]></description>
            <content:encoded><![CDATA[<h1 id="区块链技术" tabindex="-1">区块链技术 <a class="header-anchor" href="#区块链技术" aria-label="Permalink to &quot;区块链技术&quot;"></a></h1>
<p>区块链技术是一种去中心化的分布式账本技术，最初由中本聪在2008年提出，作为比特币的底层技术。它结合了分布式存储、点对点传输、共识机制和密码学等多种技术，确保数据的安全性和透明性。</p>
<p>区块链的系统架构图大致如下：</p>
<p><img src="https://cdn.ipfsscan.io/weibo/large/007CWdRmgy1ick9rzma4lj31210m6wrw.jpg" alt="image"></p>
<ul>
<li>应用层：即区块链技术的实际应用案例，由于区块链本身是一个分布式账单，被大量用于金融领域。</li>
<li>接口层：即区块链技术的编程实现，在此基础上可以开发各领域特定的区块链应用。</li>
<li>服务层：区块链技术的逻辑实现，分为许多模块。</li>
<li>调度层：主要实现区块链的分布式结构，调度各种资源。</li>
<li>资源层：区块链的算力即数据提供方。</li>
</ul>
<h2 id="基本原理" tabindex="-1">基本原理 <a class="header-anchor" href="#基本原理" aria-label="Permalink to &quot;基本原理&quot;"></a></h2>
<p>其核心特点包括：</p>
<ul>
<li><strong>去中心化</strong>：没有中央控制机构，所有参与者共同维护账本。</li>
<li><strong>不可篡改</strong>：一旦数据被写入区块链，就无法修改或删除。</li>
<li><strong>透明性</strong>：所有交易记录对所有参与者可见，增强信任。</li>
<li><strong>安全性</strong>：使用密码学保护数据传输和存储。</li>
</ul>
<h2 id="技术细节" tabindex="-1">技术细节 <a class="header-anchor" href="#技术细节" aria-label="Permalink to &quot;技术细节&quot;"></a></h2>
<ul>
<li><strong>交易生成</strong>：用户发起交易，并通过公钥和私钥进行验证。</li>
<li><strong>交易打包</strong>：有效的交易被打包成一个区块。</li>
<li><strong>共识机制</strong>：网络中的节点通过共识算法（如工作量证明或权益证明）验证交易的有效性。</li>
<li><strong>区块添加</strong>：确认后的区块被添加到现有链上，形成不可更改的记录。</li>
</ul>
<p>比特币是一种去中心化的数字货币，依赖于加密技术和社区账本来确保交易的安全性，从而消除了对银行的需求。</p>
<p>亮点</p>
<ul>
<li>💻 比特币是一种完全数字化的货币，没有政府或银行的介入。</li>
<li>🔑 加密技术通过数字签名确保交易的安全。</li>
<li>📜 社区账本追踪用户之间的交易。</li>
<li>⛏️ 新的比特币是通过一个涉及工作量证明的过程——挖矿创造出来的。</li>
<li>🔄 交易验证使用最长的区块链作为可信源。</li>
<li>💰 维护网络的矿工可以获得奖励和交易费用。</li>
<li>🌐 对潜在投资者来说，了解比特币的基础知识至关重要。
核心概念</li>
<li>🔍 去中心化：比特币在没有中央机构的情况下运作，允许用户直接进行交易，促进无信任交互。这种设计赋予个人权力并增强了隐私。</li>
<li>🔒 数字签名：交易中使用公钥和私钥防止伪造，并确保只有合法所有者可以授权支付，大大提高了安全性。</li>
<li>📊 工作量证明：共识机制依赖计算努力来验证交易，这使得修改交易历史既昂贵又耗时，从而保护了免受欺诈。</li>
<li>🏗️ 区块链结构：交易被分组成块，并按时间顺序链接，创建了一个不可更改的记录，这对于保持货币的完整性至关重要。</li>
<li>💵 有限供应：比特币的供应上限为2100万枚，这种稀缺性可能随着时间的推移对比特币的价值产生贡献，与可以无限印制的传统法定货币形成对比。</li>
<li>⚖️ 交易费用：矿工因处理交易而获得费用，这一激励系统可能导致高峰时段成本更高，与传统支付系统具有更高的吞吐量不同。</li>
<li>📚 财务素养：了解加密货币的技术基础对于做出明智的投资决策至关重要，特别是在一个以波动性和投机性为特征的市场中。</li>
</ul>
<h3 id="从一个分布式账本开始" tabindex="-1">从一个分布式账本开始 <a class="header-anchor" href="#从一个分布式账本开始" aria-label="Permalink to &quot;从一个分布式账本开始&quot;"></a></h3>
<p>分布式系统是一种将任务分配给多个独立的计算节点（计算机、服务器等）进行处理的系统架构。这种系统通过网络连接多个节点，共同完成计算任务，从而提高处理能力、可靠性和可扩展性。</p>
<p>举个简单的例子，假设有一个复杂的数学问题，如果一台计算机需要很长时间才能解决，那么将这个问题分成多个部分，每部分分配给不同的计算节点去处理，最终汇总结果，这样就能更快地解决问题。</p>
<p>而区块链的原型，就是一个分布式的账本。这个账本记录了每个参与者的交易信息，根据每笔交易记录可以计算出每个人的余额。而这个账本是可复制的、可编辑的，每个参与者都可以保存一份这个账本的副本，每个人也都有权向其中加入新的交易记录。到了月底，将大家的账本拿出来清算每人的应付与结余。</p>
<p>为了同步所有人手上的账本，每个人在新增交易记录时都应该向其他人广播消息，以确保所有人手上的账本内容一致。而在广播消息时，为了确保交易真实有效，双方应该在交易上签名确保真实。</p>
<h3 id="分布式账本可信吗" tabindex="-1">分布式账本可信吗 <a class="header-anchor" href="#分布式账本可信吗" aria-label="Permalink to &quot;分布式账本可信吗&quot;"></a></h3>
<p>如果每个人都可以随意向自己的账本中增加交易记录的话，那么到了月底时应该以谁的账本为准呢？如果有人没有接收到某笔交易的广播，或者有人恶意伪造他人签名，这时应该相信哪个版本的账本呢？</p>
<p>这时就需要引入数字加密了。与传统签名不同，数字签名会随着签署的内容而变化，这决定了每个签名的都是独一无二的。数字签名的原理很简单，每个人拥有一对 <em>公钥</em> <code>secret key</code> 和 <em>私钥</em> <code>private key</code>。在签名时，将私钥与签署内容一起进行加密计算，由于这个私钥只有本人拥有，因此不会被伪造。而其他人验证真伪时，只需要将此人的公钥与加密后的内容一起使用对应的解密算法进行计算，即可得出真伪。</p>
<p><mjx-container class="MathJax" jax="SVG" style="direction: ltr; position: relative;"><svg style="overflow: visible; min-height: 1px; min-width: 1px; vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="31.643ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 13986.2 1000" aria-hidden="true"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D446" d="M308 24Q367 24 416 76T466 197Q466 260 414 284Q308 311 278 321T236 341Q176 383 176 462Q176 523 208 573T273 648Q302 673 343 688T407 704H418H425Q521 704 564 640Q565 640 577 653T603 682T623 704Q624 704 627 704T632 705Q645 705 645 698T617 577T585 459T569 456Q549 456 549 465Q549 471 550 475Q550 478 551 494T553 520Q553 554 544 579T526 616T501 641Q465 662 419 662Q362 662 313 616T263 510Q263 480 278 458T319 427Q323 425 389 408T456 390Q490 379 522 342T554 242Q554 216 546 186Q541 164 528 137T492 78T426 18T332 -20Q320 -22 298 -22Q199 -22 144 33L134 44L106 13Q83 -14 78 -18T65 -22Q52 -22 52 -14Q52 -11 110 221Q112 227 130 227H143Q149 221 149 216Q149 214 148 207T144 186T142 153Q144 114 160 87T203 47T255 29T308 24Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(645,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(990,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(1467,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mo" transform="translate(2067,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(2456,0)"><path data-c="1D440" d="M289 629Q289 635 232 637Q208 637 201 638T194 648Q194 649 196 659Q197 662 198 666T199 671T201 676T203 679T207 681T212 683T220 683T232 684Q238 684 262 684T307 683Q386 683 398 683T414 678Q415 674 451 396L487 117L510 154Q534 190 574 254T662 394Q837 673 839 675Q840 676 842 678T846 681L852 683H948Q965 683 988 683T1017 684Q1051 684 1051 673Q1051 668 1048 656T1045 643Q1041 637 1008 637Q968 636 957 634T939 623Q936 618 867 340T797 59Q797 55 798 54T805 50T822 48T855 46H886Q892 37 892 35Q892 19 885 5Q880 0 869 0Q864 0 828 1T736 2Q675 2 644 2T609 1Q592 1 592 11Q592 13 594 25Q598 41 602 43T625 46Q652 46 685 49Q699 52 704 61Q706 65 742 207T813 490T848 631L654 322Q458 10 453 5Q451 4 449 3Q444 0 433 0Q418 0 415 7Q413 11 374 317L335 624L267 354Q200 88 200 79Q206 46 272 46H282Q288 41 289 37T286 19Q282 3 278 1Q274 0 267 0Q265 0 255 0T221 1T157 2Q127 2 95 1T58 0Q43 0 39 2T35 11Q35 13 38 25T43 40Q45 46 65 46Q135 46 154 86Q158 92 223 354T289 629Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(3507,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(3973,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(4442,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(4911,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(5440,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(5917,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"/></g><g data-mml-node="mo" transform="translate(6383,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(6827.7,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(7296.7,0)"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z" style="stroke-width: 3;"/></g><g data-mml-node="mo" transform="translate(7817.7,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z" style="stroke-width: 3;"/></g><g data-mml-node="mo" transform="translate(8484.4,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(9540.2,0)"><path data-c="1D446" d="M308 24Q367 24 416 76T466 197Q466 260 414 284Q308 311 278 321T236 341Q176 383 176 462Q176 523 208 573T273 648Q302 673 343 688T407 704H418H425Q521 704 564 640Q565 640 577 653T603 682T623 704Q624 704 627 704T632 705Q645 705 645 698T617 577T585 459T569 456Q549 456 549 465Q549 471 550 475Q550 478 551 494T553 520Q553 554 544 579T526 616T501 641Q465 662 419 662Q362 662 313 616T263 510Q263 480 278 458T319 427Q323 425 389 408T456 390Q490 379 522 342T554 242Q554 216 546 186Q541 164 528 137T492 78T426 18T332 -20Q320 -22 298 -22Q199 -22 144 33L134 44L106 13Q83 -14 78 -18T65 -22Q52 -22 52 -14Q52 -11 110 221Q112 227 130 227H143Q149 221 149 216Q149 214 148 207T144 186T142 153Q144 114 160 87T203 47T255 29T308 24Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(10185.2,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(10530.2,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(11007.2,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(11607.2,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(12136.2,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(12497.2,0)"><path data-c="1D462" d="M21 287Q21 295 30 318T55 370T99 420T158 442Q204 442 227 417T250 358Q250 340 216 246T182 105Q182 62 196 45T238 27T291 44T328 78L339 95Q341 99 377 247Q407 367 413 387T427 416Q444 431 463 431Q480 431 488 421T496 402L420 84Q419 79 419 68Q419 43 426 35T447 26Q469 29 482 57T512 145Q514 153 532 153Q551 153 551 144Q550 139 549 130T540 98T523 55T498 17T462 -8Q454 -10 438 -10Q372 -10 347 46Q345 45 336 36T318 21T296 6T267 -6T233 -11Q189 -11 155 7Q103 38 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(13069.2,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(13520.2,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"/></g></g></g></svg><mjx-assistive-mml unselectable="on" display="inline" style="top: 0px; left: 0px; clip: rect(1px, 1px, 1px, 1px); -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; position: absolute; padding: 1px 0px 0px 0px; border: 0px; display: block; width: auto; overflow: hidden;"><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>S</mi><mi>i</mi><mi>g</mi><mi>n</mi><mo stretchy="false">(</mo><mi>M</mi><mi>e</mi><mi>s</mi><mi>s</mi><mi>a</mi><mi>g</mi><mi>e</mi><mo>,</mo><mi>s</mi><mi>k</mi><mo stretchy="false">)</mo><mo>=</mo><mi>S</mi><mi>i</mi><mi>g</mi><mi>n</mi><mi>a</mi><mi>t</mi><mi>u</mi><mi>r</mi><mi>e</mi></math></mjx-assistive-mml></mjx-container></p>
<p><mjx-container class="MathJax" jax="SVG" style="direction: ltr; position: relative;"><svg style="overflow: visible; min-height: 1px; min-width: 1px; vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="45.545ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 20130.9 1000" aria-hidden="true"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D449" d="M52 648Q52 670 65 683H76Q118 680 181 680Q299 680 320 683H330Q336 677 336 674T334 656Q329 641 325 637H304Q282 635 274 635Q245 630 242 620Q242 618 271 369T301 118L374 235Q447 352 520 471T595 594Q599 601 599 609Q599 633 555 637Q537 637 537 648Q537 649 539 661Q542 675 545 679T558 683Q560 683 570 683T604 682T668 681Q737 681 755 683H762Q769 676 769 672Q769 655 760 640Q757 637 743 637Q730 636 719 635T698 630T682 623T670 615T660 608T652 599T645 592L452 282Q272 -9 266 -16Q263 -18 259 -21L241 -22H234Q216 -22 216 -15Q213 -9 177 305Q139 623 138 626Q133 637 76 637H59Q52 642 52 648Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(769,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(1235,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(1686,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(2031,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(2581,0)"><path data-c="1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mo" transform="translate(3071,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(3460,0)"><path data-c="1D440" d="M289 629Q289 635 232 637Q208 637 201 638T194 648Q194 649 196 659Q197 662 198 666T199 671T201 676T203 679T207 681T212 683T220 683T232 684Q238 684 262 684T307 683Q386 683 398 683T414 678Q415 674 451 396L487 117L510 154Q534 190 574 254T662 394Q837 673 839 675Q840 676 842 678T846 681L852 683H948Q965 683 988 683T1017 684Q1051 684 1051 673Q1051 668 1048 656T1045 643Q1041 637 1008 637Q968 636 957 634T939 623Q936 618 867 340T797 59Q797 55 798 54T805 50T822 48T855 46H886Q892 37 892 35Q892 19 885 5Q880 0 869 0Q864 0 828 1T736 2Q675 2 644 2T609 1Q592 1 592 11Q592 13 594 25Q598 41 602 43T625 46Q652 46 685 49Q699 52 704 61Q706 65 742 207T813 490T848 631L654 322Q458 10 453 5Q451 4 449 3Q444 0 433 0Q418 0 415 7Q413 11 374 317L335 624L267 354Q200 88 200 79Q206 46 272 46H282Q288 41 289 37T286 19Q282 3 278 1Q274 0 267 0Q265 0 255 0T221 1T157 2Q127 2 95 1T58 0Q43 0 39 2T35 11Q35 13 38 25T43 40Q45 46 65 46Q135 46 154 86Q158 92 223 354T289 629Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(4511,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(4977,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(5446,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(5915,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(6444,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(6921,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"/></g><g data-mml-node="mo" transform="translate(7387,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(7831.7,0)"><path data-c="1D446" d="M308 24Q367 24 416 76T466 197Q466 260 414 284Q308 311 278 321T236 341Q176 383 176 462Q176 523 208 573T273 648Q302 673 343 688T407 704H418H425Q521 704 564 640Q565 640 577 653T603 682T623 704Q624 704 627 704T632 705Q645 705 645 698T617 577T585 459T569 456Q549 456 549 465Q549 471 550 475Q550 478 551 494T553 520Q553 554 544 579T526 616T501 641Q465 662 419 662Q362 662 313 616T263 510Q263 480 278 458T319 427Q323 425 389 408T456 390Q490 379 522 342T554 242Q554 216 546 186Q541 164 528 137T492 78T426 18T332 -20Q320 -22 298 -22Q199 -22 144 33L134 44L106 13Q83 -14 78 -18T65 -22Q52 -22 52 -14Q52 -11 110 221Q112 227 130 227H143Q149 221 149 216Q149 214 148 207T144 186T142 153Q144 114 160 87T203 47T255 29T308 24Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(8476.7,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(8821.7,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(9298.7,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(9898.7,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(10427.7,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(10788.7,0)"><path data-c="1D462" d="M21 287Q21 295 30 318T55 370T99 420T158 442Q204 442 227 417T250 358Q250 340 216 246T182 105Q182 62 196 45T238 27T291 44T328 78L339 95Q341 99 377 247Q407 367 413 387T427 416Q444 431 463 431Q480 431 488 421T496 402L420 84Q419 79 419 68Q419 43 426 35T447 26Q469 29 482 57T512 145Q514 153 532 153Q551 153 551 144Q550 139 549 130T540 98T523 55T498 17T462 -8Q454 -10 438 -10Q372 -10 347 46Q345 45 336 36T318 21T296 6T267 -6T233 -11Q189 -11 155 7Q103 38 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(11360.7,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(11811.7,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"/></g><g data-mml-node="mo" transform="translate(12277.7,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(12722.3,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(13225.3,0)"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z" style="stroke-width: 3;"/></g><g data-mml-node="mo" transform="translate(13746.3,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z" style="stroke-width: 3;"/></g><g data-mml-node="mo" transform="translate(14413.1,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(15468.9,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(15829.9,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(16280.9,0)"><path data-c="1D462" d="M21 287Q21 295 30 318T55 370T99 420T158 442Q204 442 227 417T250 358Q250 340 216 246T182 105Q182 62 196 45T238 27T291 44T328 78L339 95Q341 99 377 247Q407 367 413 387T427 416Q444 431 463 431Q480 431 488 421T496 402L420 84Q419 79 419 68Q419 43 426 35T447 26Q469 29 482 57T512 145Q514 153 532 153Q551 153 551 144Q550 139 549 130T540 98T523 55T498 17T462 -8Q454 -10 438 -10Q372 -10 347 46Q345 45 336 36T318 21T296 6T267 -6T233 -11Q189 -11 155 7Q103 38 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(16852.9,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"/></g><g data-mml-node="TeXAtom" data-mjx-texclass="ORD" transform="translate(17318.9,0)"><g data-mml-node="mo"><path data-c="2F" d="M423 750Q432 750 438 744T444 730Q444 725 271 248T92 -240Q85 -250 75 -250Q68 -250 62 -245T56 -231Q56 -221 230 257T407 740Q411 750 423 750Z" style="stroke-width: 3;"/></g></g><g data-mml-node="mi" transform="translate(17818.9,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(18368.9,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(18897.9,0)"><path data-c="1D459" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(19195.9,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z" style="stroke-width: 3;"/></g><g data-mml-node="mi" transform="translate(19664.9,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z" style="stroke-width: 3;"/></g></g></g></svg><mjx-assistive-mml unselectable="on" display="inline" style="top: 0px; left: 0px; clip: rect(1px, 1px, 1px, 1px); -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; position: absolute; padding: 1px 0px 0px 0px; border: 0px; display: block; width: auto; overflow: hidden;"><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>V</mi><mi>e</mi><mi>r</mi><mi>i</mi><mi>f</mi><mi>y</mi><mo stretchy="false">(</mo><mi>M</mi><mi>e</mi><mi>s</mi><mi>s</mi><mi>a</mi><mi>g</mi><mi>e</mi><mo>,</mo><mi>S</mi><mi>i</mi><mi>g</mi><mi>n</mi><mi>a</mi><mi>t</mi><mi>u</mi><mi>r</mi><mi>e</mi><mo>,</mo><mi>p</mi><mi>k</mi><mo stretchy="false">)</mo><mo>=</mo><mi>t</mi><mi>r</mi><mi>u</mi><mi>e</mi><mrow data-mjx-texclass="ORD"><mo>/</mo></mrow><mi>f</mi><mi>a</mi><mi>l</mi><mi>s</mi><mi>e</mi></math></mjx-assistive-mml></mjx-container></p>
<p>通过签名算法得出的签名通常有 256 位的长度，而要通过控制这个签名的数据来找到能计算得出 true 的 Signature 只能通过枚举法，而 256 位的枚举法计算量是个无法企及的天文数字。</p>
<p>如果仅仅对 Message 进行加密，我们虽然无法篡改 Message，但我们可以将一条真实的交易记录复制多份，因此，在签名时通常会带上每笔交易的编号。</p>
<h2 id="应用场景" tabindex="-1">应用场景 <a class="header-anchor" href="#应用场景" aria-label="Permalink to &quot;应用场景&quot;"></a></h2>
<ul>
<li><strong>金融</strong>：用于加密货币（如比特币、以太坊）及去中心化金融（DeFi）服务。</li>
<li><strong>供应链管理</strong>：提高透明度和可追溯性，确保产品来源和质量。</li>
<li><strong>医疗健康</strong>：安全存储患者记录，提高数据共享效率。</li>
<li><strong>房地产</strong>：简化资产转让过程，通过智能合约自动执行交易。</li>
</ul>
<h2 id="未来展望" tabindex="-1">未来展望 <a class="header-anchor" href="#未来展望" aria-label="Permalink to &quot;未来展望&quot;"></a></h2>
<p>随着技术的发展，区块链有望在更多行业中发挥重要作用。尽管面临可扩展性和法规等挑战，但其去中心化、安全透明的特性使其成为改变传统商业模式的重要工具。未来，区块链可能会与人工智能、物联网等新兴技术结合，推动更广泛的创新和应用。</p>
]]></content:encoded>
            <author>Booling</author>
            <category>Development</category>
            <category>Study</category>
            <category>Web3</category>
            <enclosure url="https://cdn.ipfsscan.io/weibo/large/007CWdRmgy1ick9rzma4lj31210m6wrw.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[申请 GitHub 学生包踩坑实录]]></title>
            <link>https://blog.booling.cn/posts/life/github-student-pack</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/life/github-student-pack</guid>
            <pubDate>Wed, 06 Nov 2024 23:59:34 GMT</pubDate>
            <description><![CDATA[这个月学籍终于上学信网了，于是立刻拿来申请了一下GitHub学生包。]]></description>
            <content:encoded><![CDATA[<h1 id="申请-github-学生包踩坑实录" tabindex="-1">申请 GitHub 学生包踩坑实录 <a class="header-anchor" href="#申请-github-学生包踩坑实录" aria-label="Permalink to &quot;申请 GitHub 学生包踩坑实录&quot;"></a></h1>
<p>早早听闻 GitHub 的学生福利十分丰厚，我一直都很想申请一份玩玩，于是乎在前面的四个月中，我曾试过用录取通知书、学生证去申请，但都被机申以各种理由拒了。这个月终于能在学信网上查到学籍信息了，于是我用学信网的在线学籍认证成功申请到了 GitHub 学生包，这里记录一下踩坑过程。</p>
<h2 id="准备材料" tabindex="-1">准备材料 <a class="header-anchor" href="#准备材料" aria-label="Permalink to &quot;准备材料&quot;"></a></h2>
<p>要准备的东西很简单，但都是必须的。</p>
<h3 id="edu-邮箱" tabindex="-1">.edu 邮箱 <a class="header-anchor" href="#edu-邮箱" aria-label="Permalink to &quot;.edu 邮箱&quot;"></a></h3>
<p>这一步因学校而异，反正你要有一个由你本人控制的 .edu 邮箱，<code>.edu</code> 或者 <code>.edu.cn</code> 都可以。</p>
<h3 id="学信网学籍认证报告" tabindex="-1">学信网学籍认证报告 <a class="header-anchor" href="#学信网学籍认证报告" aria-label="Permalink to &quot;学信网学籍认证报告&quot;"></a></h3>
<p>首先，注册登录学信网并完成实名认证。然后点击导航栏的 <code>学信档案</code>，再点击右侧 <code>在线验证报告</code>，点击链接进入。这里会要求你登录学信档案，用学信网账号登录就可以。</p>
<p>此时你应该来到了这个界面</p>
<p><img src="https://cdn.ipfsscan.io/weibo/large/007CWdRmgy1icmz2ivsz8j31wm13jb1s.jpg" alt="image"></p>
<p>一般在校大学生选择第一个报告就好，点击查看，如果你没有申请过报告，会提示你申请。</p>
<p>在线验证报告申请完毕以后，将 pdf 下载下来，然后找一个可以翻译文档的工具将它翻译成英文版，并转换成 png/jpg 格式。</p>
<h2 id="填写信息" tabindex="-1">填写信息 <a class="header-anchor" href="#填写信息" aria-label="Permalink to &quot;填写信息&quot;"></a></h2>
<h3 id="个人信息" tabindex="-1">个人信息 <a class="header-anchor" href="#个人信息" aria-label="Permalink to &quot;个人信息&quot;"></a></h3>
<p>首先打开你 GitHub 的 profile，这里有几个字段需要稍微修改一下以符合申请标准。</p>
<ul>
<li>Name：填你验证报告上姓名的拼音</li>
<li>Bio：加一句类似 <em>Student of xxxx</em> 的介绍</li>
<li>Company：填你学校的英文全称或者缩写</li>
<li>Location：选择你学校所在的地区</li>
</ul>
<h3 id="付款信息" tabindex="-1">付款信息 <a class="header-anchor" href="#付款信息" aria-label="Permalink to &quot;付款信息&quot;"></a></h3>
<p>打开你 GitHub 账号的 Settings 页面，点击 <code>Billing and plans</code>，再点击 <code>Paymant information</code>，点击编辑。</p>
<ul>
<li>First name/Last name：如上，填入姓名拼音</li>
<li>Address：直接填写大学名称或校区地址</li>
<li>City/Country：同上</li>
<li>State/Province：有些大学在申请时可能会检查此字段，最好也如实填写</li>
</ul>
<h3 id="绑定邮箱" tabindex="-1">绑定邮箱 <a class="header-anchor" href="#绑定邮箱" aria-label="Permalink to &quot;绑定邮箱&quot;"></a></h3>
<p>还是在 Settings 页面，点击 <code>Email</code>，将你的 <code>.edu</code> 邮箱添加到账号，并查收验证邮件完成验证。</p>
<blockquote>
<p>这一步非常重要，一定要完成 <code>.edu</code> 邮箱的验证，否则可能导致申请被拒。</p>
</blockquote>
<h2 id="提交申请" tabindex="-1">提交申请 <a class="header-anchor" href="#提交申请" aria-label="Permalink to &quot;提交申请&quot;"></a></h2>
<p>验证过 <code>.edu</code> 邮箱以后，回到 Dashboard 页，应该能看到 GitHub 邀请你加入 Education 计划的卡片了，可以直接点击申请。</p>
<p>这里建议关掉所有代理软件并且使用校园网访问，保证 IP 地址和定位是在校内。</p>
<p>在申请页，如实填写所有字段，最好全部使用英文及官方译名，在上传凭证时上传刚才准备好的英文版验证报告，Proof Type 选择 Others。</p>
<p>这里可能会要你提供对于证明的描述，用英文写一句 <code>Official proof of CHSI</code> 就可以了。</p>
<p>点击提交申请，理论上可以直接通过，如果被 Reject 了 GitHub 会显示原因并给出相应的提示，按照提示修改以后重新提交就可以了。</p>
<p>在申请通过的三天内，经过人工复核，GitHub 会向你的 <code>.edu</code> 邮箱发送邮件提醒你你的学生权益已经成功激活，现在你就可以愉快白嫖各种学生福利啦。<s>我在正好第三天结束时才收到邮件</s></p>
]]></content:encoded>
            <author>Booling</author>
            <category>Life</category>
            <category>Study</category>
            <category>GitHub</category>
            <enclosure url="https://cdn.ipfsscan.io/weibo/large/007CWdRmgy1icmz2ivsz8j31wm13jb1s.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[JavaScript 中 var 与 let 的区别]]></title>
            <link>https://blog.booling.cn/posts/development/var-vs-let</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/var-vs-let</guid>
            <pubDate>Sun, 27 Oct 2024 20:48:18 GMT</pubDate>
            <description><![CDATA[一直不太清楚 JavaScript 中 var 与 let 的区别，于是今天好好了解了一下……]]></description>
            <content:encoded><![CDATA[<h1 id="javascript-中-var-与-let-的区别" tabindex="-1">JavaScript 中 <code>var</code> 与 <code>let</code> 的区别 <a class="header-anchor" href="#javascript-中-var-与-let-的区别" aria-label="Permalink to &quot;JavaScript 中 `var` 与 `let` 的区别&quot;"></a></h1>
<p>一直不太清楚 JavaScript 中 var 与 let 的区别，于是今天好好了解了一下，主要有以下三大区别。</p>
<h2 id="作用域" tabindex="-1">作用域 <a class="header-anchor" href="#作用域" aria-label="Permalink to &quot;作用域&quot;"></a></h2>
<p>首先是作用域上的区别，<code>var</code> 的作用域是函数级别（function level）的，而 <code>let</code> 的作用域是块级别（block level）的。</p>
<p>如下面的例子：</p>
<div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> foo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() { </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// function level start</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) { </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// block level start</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">		var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> c </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">		let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> d </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	} </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// block level end</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// this works well</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(c)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// ReferenceError: d is not defined</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(d)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">} </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// function level end</span></span></code></pre>
</div><p>可以看到，由 <code>let</code> 定义的变量只有在同一块级作用域内才能访问到，而由 <code>var</code> 定义的变量则在同一函数级作用域中可以访问到。</p>
<h2 id="重声明" tabindex="-1">重声明 <a class="header-anchor" href="#重声明" aria-label="Permalink to &quot;重声明&quot;"></a></h2>
<p><code>var</code> 声明的变量是可以被重新声明的，而且作用域与被重声明前相同。需要注意的是，在开发过程中要谨慎使用这一特性，它可能导致一些无法预测的错误。而 <code>let</code> 声明的变量是不允许重声明的，尝试重声明一个 <code>let</code> 变量会引发语法错误，提示你不应该重声明一个 <code>let</code> 变量。</p>
<div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// declaration</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// redeclaration</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // acceptable</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 2</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // error</span></span></code></pre>
</div><h2 id="变量提升" tabindex="-1">变量提升 <a class="header-anchor" href="#变量提升" aria-label="Permalink to &quot;变量提升&quot;"></a></h2>
<p>使用 <code>var</code> 声明的变量会被提升到其直属函数级作用域中，这意味着你可以在声明并初始化一个变量前引用它，但其值为 <code>undefined</code>。而 <code>let</code> 变量则不会被提升到函数级作用域中，你只能在声明它以后进行引用。</p>
<div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Using var</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Output: undefined (due to hoisting)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Output: 5</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Using let</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(b)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Output: ReferenceError: Cannot access 'b' before initialization</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 10</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(b)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Output: 10</span></span></code></pre>
</div><p>这里顺便引入 Temporal Dead Zone 的概念。TDZ 指的是一个从块级作用域的头端开始，到一个变量被初始化结束的区域。在 JavaScript 中，由 <code>let</code> 和 <code>const</code> 声明的变量是有 TDZ 的，而由 <code>var</code> 声明的变量由于经过了提升，则不具有 TDZ。</p>
<div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> foo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(myVar)</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">	// ReferenceError: Cannot access 'myVar' before initialization</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	let</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> myVar </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "Hello, World!"</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // same as const</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(myVar) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Outputs: Hello, World!</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> bar</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(myVar) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Outputs: undefined</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">	var</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> myVar </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "Hello, World!"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">	console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(myVar) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Outputs: Hello, World! </span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div>]]></content:encoded>
            <author>Booling</author>
            <category>Development</category>
            <category>FrontEnd</category>
            <category>Study</category>
        </item>
        <item>
            <title><![CDATA[配置 ESLint + Prettier 环境来规范代码风格]]></title>
            <link>https://blog.booling.cn/posts/development/eslint-prettier</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/development/eslint-prettier</guid>
            <pubDate>Sun, 21 Aug 2022 11:07:35 GMT</pubDate>
            <description><![CDATA[记录一次在前端项目中配置 ESLint + Prettier 来规范代码风格的过程。]]></description>
            <content:encoded><![CDATA[<h1 id="配置-eslint-prettier-环境来规范代码风格" tabindex="-1">配置 <code>ESLint</code> + <code>Prettier</code> 环境来规范代码风格 <a class="header-anchor" href="#配置-eslint-prettier-环境来规范代码风格" aria-label="Permalink to &quot;配置 `ESLint` + `Prettier` 环境来规范代码风格&quot;"></a></h1>
<p>前端开发过程中，每个人都有自己的代码风格，但项目中应该将代码风格统一，所以我习惯用 <code>ESLint</code> + <code>Prettier</code> 来格式化代码。</p>
<h2 id="介绍" tabindex="-1">介绍 <a class="header-anchor" href="#介绍" aria-label="Permalink to &quot;介绍&quot;"></a></h2>
<p><code>ESLint</code> 是一个 <code>JavaScript</code> 代码检测工具，用以进行一系列代码质量检测。</p>
<p><code>Prettier</code> 是一个前端代码格式化工具，用以进行代码格式化操作。</p>
<p>但 <code>ESLint</code> 的格式化支持的文件类型较少，所以我喜欢配合 <code>Prettier</code> 来进行格式化。</p>
<h2 id="配置-eslint" tabindex="-1">配置 <code>ESLint</code> <a class="header-anchor" href="#配置-eslint" aria-label="Permalink to &quot;配置 `ESLint`&quot;"></a></h2>
<h3 id="安装-eslint" tabindex="-1">安装 <code>ESLint</code> <a class="header-anchor" href="#安装-eslint" aria-label="Permalink to &quot;安装 `ESLint`&quot;"></a></h3>
<div class="language-shell vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># yarn yes!</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">yarn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eslint</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># npm</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -D</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eslint</span></span></code></pre>
</div><h3 id="初始化-eslint" tabindex="-1">初始化 <code>ESLint</code> <a class="header-anchor" href="#初始化-eslint" aria-label="Permalink to &quot;初始化 `ESLint`&quot;"></a></h3>
<div class="language-shell vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">eslint</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> --init</span></span></code></pre>
</div><p>此处根据你的项目具体情况回答几个问题，<code>ESLint</code> 就会自己乖乖生成配置文件了。</p>
<h3 id="配置脚本" tabindex="-1">配置脚本 <a class="header-anchor" href="#配置脚本" aria-label="Permalink to &quot;配置脚本&quot;"></a></h3>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// package.json</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">...</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "scripts"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "lint"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"eslint --fix ./**"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">...</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><p>再执行 <code>yarn lint</code> 就可以进行错误检测和格式化啦~</p>
<h2 id="配置-prettier" tabindex="-1">配置 <code>Prettier</code> <a class="header-anchor" href="#配置-prettier" aria-label="Permalink to &quot;配置 `Prettier`&quot;"></a></h2>
<p>由于 <code>ESLint</code> 和 <code>Prettier</code> 的格式化功能有冲突，所以我们需要使用 <code>ESLint</code> 插件来关闭 <code>ESLint</code> 的格式化功能，防止 <code>Prettier</code> 和 <code>ESLint</code> 冲突。</p>
<h3 id="安装-prettier-及-eslint-插件" tabindex="-1">安装 <code>Prettier</code> 及 <code>ESLint</code> 插件 <a class="header-anchor" href="#安装-prettier-及-eslint-插件" aria-label="Permalink to &quot;安装 `Prettier` 及 `ESLint` 插件&quot;"></a></h3>
<div class="language-shell vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">yarn</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> prettier</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eslint-plugin-prettier</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> eslint-config-prettier</span></span></code></pre>
</div><h3 id="修改-eslint-配置文件" tabindex="-1">修改 <code>ESLint</code> 配置文件 <a class="header-anchor" href="#修改-eslint-配置文件" aria-label="Permalink to &quot;修改 `ESLint` 配置文件&quot;"></a></h3>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// .eslintrc.json</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">...</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "extends"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">    "plugin:prettier/recommended"</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  ]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">...</span></span></code></pre>
</div><h3 id="配置-prettier-1" tabindex="-1">配置 <code>Prettier</code> <a class="header-anchor" href="#配置-prettier-1" aria-label="Permalink to &quot;配置 `Prettier`&quot;"></a></h3>
<p>在项目根目录新建 <code>.prettierrc</code> 文件</p>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "printWidth"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">80</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 自动换行长度</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "tabWidth"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 制表符大小</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "useTabs"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// Tab 缩进</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "singleQuote"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 单引号</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "semi"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 行末分号</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "trailingComma"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"none"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 尾逗号</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "bracketSpacing"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // 大括号环绕空格 eg.{ foo }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><h3 id="配置脚本-1" tabindex="-1">配置脚本 <a class="header-anchor" href="#配置脚本-1" aria-label="Permalink to &quot;配置脚本&quot;"></a></h3>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// package.json</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">...</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">  "scripts"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "format"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"prettier --write ."</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">...</span></span></code></pre>
</div><h2 id="配置-vscode" tabindex="-1">配置 <code>VSCode</code> <a class="header-anchor" href="#配置-vscode" aria-label="Permalink to &quot;配置 `VSCode`&quot;"></a></h2>
<p>上面的配置都是进行运行时检测，为了我们的写码体验，可以安装一些 <code>VSCode</code> 插件来实时检测。</p>
<h3 id="安装-vscode-eslint-插件" tabindex="-1">安装 <code>VSCode ESLint</code> 插件 <a class="header-anchor" href="#安装-vscode-eslint-插件" aria-label="Permalink to &quot;安装 `VSCode ESLint` 插件&quot;"></a></h3>
<div class="language-txt vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>名称: ESLint   </span></span>
<span class="line"><span>ID: dbaeumer.vscode-eslint   </span></span>
<span class="line"><span>说明: Integrates ESLint JavaScript into VS Code.   </span></span>
<span class="line"><span>版本: 2.2.6   </span></span>
<span class="line"><span>发布者: Microsoft   </span></span>
<span class="line"><span>[VS Marketplace 链接](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)    </span></span>
<span class="line"><span>这个插件将会自动用项目根目录下的 `.eslintrc.*` 配置文件来格式化代码</span></span></code></pre>
</div><h3 id="安装-vscode-prettier-插件" tabindex="-1">安装 <code>VSCode Prettier</code> 插件 <a class="header-anchor" href="#安装-vscode-prettier-插件" aria-label="Permalink to &quot;安装 `VSCode Prettier` 插件&quot;"></a></h3>
<div class="language-txt vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>名称: Prettier - Code formatter   </span></span>
<span class="line"><span>ID: esbenp.prettier-vscode   </span></span>
<span class="line"><span>说明: Code formatter using prettier   </span></span>
<span class="line"><span>版本: 9.8.0   </span></span>
<span class="line"><span>发布者: Prettier   </span></span>
<span class="line"><span>[VS Marketplace 链接](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)</span></span></code></pre>
</div><h3 id="安装-vscode-prettier-eslint-插件" tabindex="-1">安装 <code>VSCode Prettier ESLint</code> 插件 <a class="header-anchor" href="#安装-vscode-prettier-eslint-插件" aria-label="Permalink to &quot;安装 `VSCode Prettier ESLint` 插件&quot;"></a></h3>
<div class="language-txt vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span>名称: Prettier ESLint</span></span>
<span class="line"><span>ID: rvest.vs-code-prettier-eslint</span></span>
<span class="line"><span>说明: A Visual Studio Extension to format JavaScript and Typescript code using prettier-eslint package</span></span>
<span class="line"><span>版本: 5.0.4</span></span>
<span class="line"><span>发布者: Rebecca Vest</span></span>
<span class="line"><span>[VS Marketplace 链接](https://marketplace.visualstudio.com/items?itemName=rvest.vs-code-prettier-eslint)</span></span></code></pre>
</div><h3 id="修改-vscode-设置" tabindex="-1">修改 <code>VSCode</code> 设置 <a class="header-anchor" href="#修改-vscode-设置" aria-label="Permalink to &quot;修改 `VSCode` 设置&quot;"></a></h3>
<div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "editor.defaultFormatter"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"rvest.vs-code-prettier-eslint"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">//  默认使用 prettier 作为格式化工具</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "editor.formatOnSave"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">true</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> // 保存代码时格式化</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
</div><h2 id="配置完成🎉" tabindex="-1">配置完成🎉 <a class="header-anchor" href="#配置完成🎉" aria-label="Permalink to &quot;配置完成🎉&quot;"></a></h2>
<p>接下来就可以愉快写码啦~</p>
]]></content:encoded>
            <author>Booling</author>
            <category>Development</category>
            <category>FrontEnd</category>
            <category>Study</category>
        </item>
        <item>
            <title><![CDATA[Windows 下添加小鹤双拼]]></title>
            <link>https://blog.booling.cn/posts/life/xiaohe</link>
            <guid isPermaLink="false">https://blog.booling.cn/posts/life/xiaohe</guid>
            <pubDate>Fri, 12 Aug 2022 10:08:51 GMT</pubDate>
            <description><![CDATA[如何通过修改注册表来为 Windows 添加小鹤双拼码表。]]></description>
            <content:encoded><![CDATA[<h2 id="windows-下添加小鹤双拼" tabindex="-1">Windows 下添加小鹤双拼 <a class="header-anchor" href="#windows-下添加小鹤双拼" aria-label="Permalink to &quot;Windows 下添加小鹤双拼&quot;"></a></h2>
<p>双拼最近越来越火了，我也在尝试学习，但是 Windows 自带的微软拼音输入法却没有自带小鹤双拼码表，如果自己一个键一个键的添加不仅麻烦还容易出错，所以我找到了这种修改注册表的添加方式。</p>
<h3 id="手动添加" tabindex="-1">手动添加 <a class="header-anchor" href="#手动添加" aria-label="Permalink to &quot;手动添加&quot;"></a></h3>
<p>打开 Windows Powershell，执行以下命令：</p>
<div class="language-powershell vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">powershell</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">New-ItemProperty</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> HKCU:\Software\Microsoft\InputMethod\Settings\CHS\ </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">name </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"UserDefinedDoublePinyinScheme0"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">value </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"小鹤双拼*2*^*iuvdjhcwfg^xmlnpbksqszxkrltvyovt"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">propertyType string </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># 添加小鹤码表</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">gpupdate </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">force </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">wait:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"> # 刷新注册表</span></span></code></pre>
</div><h3 id="自动添加" tabindex="-1">自动添加 <a class="header-anchor" href="#自动添加" aria-label="Permalink to &quot;自动添加&quot;"></a></h3>
<p>可以在本站 <a href="https://blog.booling.cn/files/xiaohe.ps1" target="_blank" rel="noreferrer">此处</a> 下载 <code>.ps1</code> 脚本，运行即可。</p>
<h3 id="使用" tabindex="-1">使用 <a class="header-anchor" href="#使用" aria-label="Permalink to &quot;使用&quot;"></a></h3>
<p>添加完毕后即可在 <code>Windows 设置/时间和语言/语言和区域/微软拼音输入法/常规</code> 中选择 <code>小鹤双拼</code> 使用。</p>
]]></content:encoded>
            <author>Booling</author>
            <category>Life</category>
            <category>System</category>
            <category>IME</category>
        </item>
    </channel>
</rss>